diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..6cd5f16 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,18 @@ +.env +.vscode + +*.egg-info +*.pot +*.py[co] +.tox/ +__pycache__ +MANIFEST +dist/ +docs/_build/ +docs/locale/ +node_modules/ +tests/coverage_html/ +tests/.coverage +build/ +tests/report/ +db.sqlite3 \ No newline at end of file diff --git a/backend/Pipfile b/backend/Pipfile index 26ee535..c2e0e73 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -5,6 +5,13 @@ name = "pypi" [packages] django = "*" +djangorestframework = "*" +djangorestframework-simplejwt = "*" +psycopg2-binary = "*" +python-dotenv = "*" +django-tinymce = "*" +django-cors-headers = "*" +whitenoise = "*" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 220b9b7..a8de510 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "99c4b9ec1b8891ff787677276760beb6d6d4919c55660da1c713682156a6086c" + "sha256": "4b1bbf32af393a67f2d6a8f086509f5009f5eac8d67ecaf42a63d6a1e0b6167d" }, "pipfile-spec": 6, "requires": { @@ -21,7 +21,6 @@ "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" ], - "markers": "python_version >= '3.5'", "version": "==3.3.1" }, "django": { @@ -32,6 +31,94 @@ "index": "pypi", "version": "==3.1.7" }, + "django-cors-headers": { + "hashes": [ + "sha256:1ac2b1213de75a251e2ba04448da15f99bcfcbe164288ae6b5ff929dc49b372f", + "sha256:96069c4aaacace786a34ee7894ff680780ec2644e4268b31181044410fecd12e" + ], + "index": "pypi", + "version": "==3.7.0" + }, + "django-tinymce": { + "hashes": [ + "sha256:3684d6611162cd3566b068cfeaf9309d415f1d415191a1f8a8c9140246774679", + "sha256:77cca137e97e92e43e42c98a232df3e66b80c987ad0f03709a4b73435f8e4060" + ], + "index": "pypi", + "version": "==3.3.0" + }, + "djangorestframework": { + "hashes": [ + "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf", + "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2" + ], + "index": "pypi", + "version": "==3.12.4" + }, + "djangorestframework-simplejwt": { + "hashes": [ + "sha256:7adc913ba0d2ed7f46e0b9bf6e86f9bd9248f1c4201722b732b8213e0ea66f9f", + "sha256:bd587700b6ab34a6c6b12d426cce4fa580d57ef1952ad4ba3b79707784619ed3" + ], + "index": "pypi", + "version": "==4.6.0" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", + "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", + "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", + "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", + "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", + "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", + "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", + "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", + "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", + "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", + "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", + "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", + "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", + "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", + "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", + "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", + "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", + "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", + "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", + "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", + "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", + "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", + "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", + "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", + "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", + "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", + "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", + "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", + "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", + "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", + "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", + "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", + "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", + "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" + ], + "index": "pypi", + "version": "==2.8.6" + }, + "pyjwt": { + "hashes": [ + "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7", + "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847" + ], + "version": "==2.0.1" + }, + "python-dotenv": { + "hashes": [ + "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec", + "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4" + ], + "index": "pypi", + "version": "==0.16.0" + }, "pytz": { "hashes": [ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", @@ -44,8 +131,15 @@ "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "markers": "python_version >= '3.5'", "version": "==0.4.1" + }, + "whitenoise": { + "hashes": [ + "sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7", + "sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d" + ], + "index": "pypi", + "version": "==5.2.0" } }, "develop": {} diff --git a/backend/politalk/politalk/__init__.py b/backend/api/__init__.py similarity index 100% rename from backend/politalk/politalk/__init__.py rename to backend/api/__init__.py diff --git a/backend/api/admin.py b/backend/api/admin.py new file mode 100644 index 0000000..1469f77 --- /dev/null +++ b/backend/api/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from .models import Profile, Article, Event + +# Register your models here. + +admin.site.register(Profile) +admin.site.register(Article) +admin.site.register(Event) \ No newline at end of file diff --git a/backend/api/apps.py b/backend/api/apps.py new file mode 100644 index 0000000..d87006d --- /dev/null +++ b/backend/api/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + name = 'api' diff --git a/backend/api/migrations/0001_initial.py b/backend/api/migrations/0001_initial.py new file mode 100644 index 0000000..04b85bc --- /dev/null +++ b/backend/api/migrations/0001_initial.py @@ -0,0 +1,48 @@ +# Generated by Django 3.1.7 on 2021-03-28 13:20 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import tinymce.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('headline', models.CharField(blank=True, max_length=100, null=True)), + ('content', tinymce.models.HTMLField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(blank=True, max_length=50, null=True)), + ('date', models.DateField(blank=True, null=True)), + ('organizer', models.CharField(blank=True, max_length=50, null=True)), + ('address', models.CharField(blank=True, max_length=50, null=True)), + ('description', models.TextField(blank=True, max_length=2000, null=True)), + ], + ), + 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)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Profiles', + }, + ), + ] diff --git a/backend/master b/backend/api/migrations/__init__.py similarity index 100% rename from backend/master rename to backend/api/migrations/__init__.py diff --git a/backend/api/models.py b/backend/api/models.py new file mode 100644 index 0000000..a4b57db --- /dev/null +++ b/backend/api/models.py @@ -0,0 +1,37 @@ +from django.contrib.auth.models import User +from django.db import models +from tinymce.models import HTMLField + +from .models import * + +# Create your models here. + +class Profile (models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + nickname = models.CharField(max_length=20, blank=True, null=True) + + def __str__(self): + return f'{self.user.username}\'s profile' + + class Meta: + verbose_name_plural = "Profiles" + +class Article(models.Model): + headline = models.CharField(max_length=100, blank=True, null=True) + content = HTMLField(blank=True, null=True) + + # NLP Data + + def __str__(self): + return self.headline + +class Event(models.Model): + name = models.CharField(max_length=50, blank=True, null=True) + date = models.DateField(blank=True, null=True) + organizer = models.CharField(max_length=50, blank=True, null=True) + address = models.CharField(max_length=50, blank=True, null=True) + description = models.TextField(max_length=2000, blank=True, null=True) + + + def __str__(self): + return self.name diff --git a/backend/api/serializers.py b/backend/api/serializers.py new file mode 100644 index 0000000..d51af41 --- /dev/null +++ b/backend/api/serializers.py @@ -0,0 +1,49 @@ +from rest_framework import serializers +from django.contrib.auth.models import User + +from .models import Profile, Article, Event + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('username', 'email', 'first_name', 'last_name') + +class UserCreateSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True) + + def create(self, validated_data): + 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 ProfileSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta: + model = Profile + fields = ('user', 'nickname',) + +class ArticleSerializer(serializers.ModelSerializer): + class Meta: + model = Article + fields = ('headline', 'content',) + +class EventSerializer(serializers.ModelSerializer): + class Meta: + model = Event + fields = ('name', 'date', 'organizer', 'address', 'description',) \ No newline at end of file diff --git a/backend/api/tests.py b/backend/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/api/urls.py b/backend/api/urls.py new file mode 100644 index 0000000..196a836 --- /dev/null +++ b/backend/api/urls.py @@ -0,0 +1,20 @@ +from . import views + +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, +) + +urlpatterns = [ + path('profile/create', views.UserProfileCreate.as_view()), + path('profile/', views.UserProfileDetail.as_view()), + path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('event/', views.EventViewSet.as_view({'get': 'retrieve'})), + path('event/', views.EventViewSet.as_view({'get': 'list', 'post': 'create'})), + path('article/', views.ArticleViewSet.as_view({'get': 'retrieve'})), + path('article/', views.ArticleViewSet.as_view({'get': 'list', 'post': 'create'})) +] \ No newline at end of file diff --git a/backend/api/views.py b/backend/api/views.py new file mode 100644 index 0000000..d78aaaa --- /dev/null +++ b/backend/api/views.py @@ -0,0 +1,31 @@ +from django.shortcuts import render + +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 * + +# Create your views here. + +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 + +class EventViewSet(ModelViewSet): + queryset = Event.objects.all() + serializer_class = EventSerializer + +class ArticleViewSet(ModelViewSet): + queryset = Event.objects.all() + serializer_class = ArticleSerializer \ No newline at end of file diff --git a/backend/config/.env.sample b/backend/config/.env.sample new file mode 100644 index 0000000..e7e8f57 --- /dev/null +++ b/backend/config/.env.sample @@ -0,0 +1,4 @@ +SECRET_KEY=SECRET_KEY +DEBUG=True +DB_USER= +DB_PASS= \ No newline at end of file diff --git a/backend/config/__init__.py b/backend/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/politalk/politalk/asgi.py b/backend/config/asgi.py similarity index 82% rename from backend/politalk/politalk/asgi.py rename to backend/config/asgi.py index b23d186..295fc5e 100644 --- a/backend/politalk/politalk/asgi.py +++ b/backend/config/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'politalk.settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') application = get_asgi_application() diff --git a/backend/politalk/politalk/settings.py b/backend/config/settings.py similarity index 70% rename from backend/politalk/politalk/settings.py rename to backend/config/settings.py index 9eb991b..0ff3a2f 100644 --- a/backend/politalk/politalk/settings.py +++ b/backend/config/settings.py @@ -11,10 +11,13 @@ https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +from dotenv import load_dotenv +load_dotenv() # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ @@ -31,16 +34,25 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + 'api', + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + 'tinymce', + 'corsheaders', + 'rest_framework' ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -49,7 +61,18 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -ROOT_URLCONF = 'politalk.urls' +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + ), +} + +ROOT_URLCONF = 'config.urls' TEMPLATES = [ { @@ -67,7 +90,7 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = 'politalk.wsgi.application' +# WSGI_APPLICATION = 'config.wsgi.application' # Database @@ -80,6 +103,26 @@ DATABASES = { } } +if DEBUG: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'openly', + 'USER': os.getenv("DB_USER"), + 'PASSWORD': os.getenv("DB_PASS"), + 'HOST': 'localhost', + 'PORT': '5432', + } + } + + # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators @@ -118,3 +161,7 @@ USE_TZ = True # 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/' \ No newline at end of file diff --git a/backend/politalk/politalk/urls.py b/backend/config/urls.py similarity index 70% rename from backend/politalk/politalk/urls.py rename to backend/config/urls.py index bf6843c..3fd7c9d 100644 --- a/backend/politalk/politalk/urls.py +++ b/backend/config/urls.py @@ -13,9 +13,17 @@ 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('api/', include('api.urls')), + path('tinymce/', include('tinymce.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 diff --git a/backend/politalk/politalk/wsgi.py b/backend/config/wsgi.py similarity index 82% rename from backend/politalk/politalk/wsgi.py rename to backend/config/wsgi.py index e1dd8a2..2a21232 100644 --- a/backend/politalk/politalk/wsgi.py +++ b/backend/config/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'politalk.settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') application = get_wsgi_application() diff --git a/backend/politalk/manage.py b/backend/manage.py old mode 100644 new mode 100755 similarity index 89% rename from backend/politalk/manage.py rename to backend/manage.py index 26f5a86..8e7ac79 --- a/backend/politalk/manage.py +++ b/backend/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'politalk.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: