From 951af864f2d71a1caee6717e9581a69e24cfcaf0 Mon Sep 17 00:00:00 2001 From: Muhammad Rofi Date: Sun, 4 Oct 2020 07:15:52 +0700 Subject: [PATCH 1/5] Initiate project with environment --- Dockerfile | 7 + api/__init__.py | 0 api/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 112 bytes api/__pycache__/admin.cpython-38.pyc | Bin 0 -> 153 bytes api/__pycache__/apps.cpython-38.pyc | Bin 0 -> 326 bytes api/__pycache__/models.cpython-38.pyc | Bin 0 -> 150 bytes api/admin.py | 3 + api/apps.py | 5 + api/migrations/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 123 bytes api/models.py | 3 + api/tests.py | 3 + api/views.py | 3 + codingchallenge/__init__.py | 0 .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 124 bytes .../__pycache__/settings.cpython-38.pyc | Bin 0 -> 2327 bytes .../__pycache__/urls.cpython-38.pyc | Bin 0 -> 909 bytes .../__pycache__/wsgi.cpython-38.pyc | Bin 0 -> 543 bytes codingchallenge/asgi.py | 16 +++ codingchallenge/settings.py | 122 ++++++++++++++++++ codingchallenge/urls.py | 21 +++ codingchallenge/wsgi.py | 16 +++ db.sqlite3 | 0 docker-compose.yml | 17 +++ instruction.md | 1 + manage.py | 22 ++++ requirements.txt | 3 + 27 files changed, 242 insertions(+) create mode 100644 Dockerfile create mode 100644 api/__init__.py create mode 100644 api/__pycache__/__init__.cpython-38.pyc create mode 100644 api/__pycache__/admin.cpython-38.pyc create mode 100644 api/__pycache__/apps.cpython-38.pyc create mode 100644 api/__pycache__/models.cpython-38.pyc create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/migrations/__init__.py create mode 100644 api/migrations/__pycache__/__init__.cpython-38.pyc create mode 100644 api/models.py create mode 100644 api/tests.py create mode 100644 api/views.py create mode 100644 codingchallenge/__init__.py create mode 100644 codingchallenge/__pycache__/__init__.cpython-38.pyc create mode 100644 codingchallenge/__pycache__/settings.cpython-38.pyc create mode 100644 codingchallenge/__pycache__/urls.cpython-38.pyc create mode 100644 codingchallenge/__pycache__/wsgi.cpython-38.pyc create mode 100644 codingchallenge/asgi.py create mode 100644 codingchallenge/settings.py create mode 100644 codingchallenge/urls.py create mode 100644 codingchallenge/wsgi.py create mode 100644 db.sqlite3 create mode 100644 docker-compose.yml create mode 100644 instruction.md create mode 100755 manage.py create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f25f983 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3 +ENV PYTHONUNBUFFERED=1 +RUN mkdir /code +WORKDIR /code +COPY requirements.txt /code/ +RUN pip install -r requirements.txt +COPY . /code/ diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/__pycache__/__init__.cpython-38.pyc b/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6924934f8809e6b7eecb00614fa9a4ac26867dc5 GIT binary patch literal 112 zcmWIL<>g`kf>Pnico6*pPZkPs-IYp hsUIJonU`4-AFo$Xd5gm)H$SB`C)Ez5{4)?U002g`kf>PniczYoI7{oyaOhAqU5Elyoi4=wu#vF!R#wbQch7_h?22JLdKv4!w z##^k3DY=<>ews{Cd?{IpdFlCj$@zIDMVU!OAOlu16fpxSF!4)BKRG`oRX?#HQy;8J Yub}c4hfQvNN@-529V1ZLXCP((0F4+QegFUf literal 0 HcmV?d00001 diff --git a/api/__pycache__/apps.cpython-38.pyc b/api/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce858b4a3472605c4a1541a6567104b92ea35d2d GIT binary patch literal 326 zcmYjNJ5Iwu5S{TF$@xhjE;WlUt`&tl6(YPbwn+LnqdQ1Qk_ zA|uV`?VEYKv(Ky5oM6NCNgk2E44hjGu!HwE2ujKo)toWY18Jo0gVYuL?y^YZCrU5A z(M2hT*`4?IZSztd4|Z~H46uXumk0%Atb}RKoJLS^Hd;=j!R5Z(r*TL`n!GMV!d#?o zb*QXfio6YZ^~c1PKdGBRbqqgOn?tL`1{!eRul=cWAZ?>QLP&VTd)&Fu*StBlYwPQb W8<~Htsj{C6$?f!jt?e=DEZr|(c0&RH literal 0 HcmV?d00001 diff --git a/api/__pycache__/models.cpython-38.pyc b/api/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e65469d2e8119a8841decac5c44a045123e2764a GIT binary patch literal 150 zcmWIL<>g`kf>PnicuOGt7{oyaOhAqU5Elyoi4=wu#vF!R#wbQch7_h?22JLdKv4!w z##?N;`6;P6#eSMhw>VR>67$mY^-_|GK$=%F6tMs)F!4)RKN+Y_Kd~TFAEH68pz;=n R4Uk=$lWNBZRQDN(82~WZ9>M?s literal 0 HcmV?d00001 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..d87006d --- /dev/null +++ b/api/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + name = 'api' diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/migrations/__pycache__/__init__.cpython-38.pyc b/api/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..554e3d4d55b99e7d8718c9f07063a909645f94aa GIT binary patch literal 123 zcmWIL<>g`kf>Pnico6*2KczG$)efZdGY~TX0Bunis{jB1 literal 0 HcmV?d00001 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/api/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/codingchallenge/__init__.py b/codingchallenge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codingchallenge/__pycache__/__init__.cpython-38.pyc b/codingchallenge/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ff65663af340cae914c8737b76c961eaddbd7a6 GIT binary patch literal 124 zcmWIL<>g`kg6~3=@gVv!h(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o2BzKRG`o6+~p_ qr6*@3=H#U2rKjr0$7kkcmc+;F6;$5hu*uC&Da}c>18MyX#0&t(7a5)a literal 0 HcmV?d00001 diff --git a/codingchallenge/__pycache__/settings.cpython-38.pyc b/codingchallenge/__pycache__/settings.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7616bf608e1d3d524bfaace3226de48c6a4db763 GIT binary patch literal 2327 zcmb7F-E!Mh5SAUqcHB5klk|rcsM1mzr$la>4itt$Ra;Fx_1{Ex1G>@3+GG19`m4@y zljxS$=mlKz77UM}TdsHk2AB?IB|C}hbcP^V$LH+X{dUi%-BqVh$P0M=vV3Ti=LO-n zcrkh9aPb%)_A3q`=mMl5ZKs^Ho=yoQO=f;b{V4tKT~Pvmoj@nC++ZImkd3 zaxe#ZI0y4k=;!o2EWmkKgbT0)%dpbV>gV91J`b1r0*G)KuD}Oy6|VI~nt~5si-=Ny zkMsrj7_O5HNY?~deVwK${XE=&o2YXGK1s$~$@nQv^+kOVZtE9dO<%%DmhoA^=OPsK zOR%m9#XIJMlpy3QyQbUsBt|%=Zl6g#FOVz`aBdAu+a_+GNPgh$5{t|E{1$OZU~&Rd z_fWbGiS3374s|8Q&47S?yPoi?(ZGmy6Jf@ z?M-LZ{4yYGj}8v*5~F|DdN8opETbKz*29(2$z;oOc|f~z!eY2QEghnZYg5A5gt(j^ z`h-(zsJR}`b{ z6Q%rIZB%rzQr*^~oYHC;$t>G!wX4mB7EU)Vd7t%@>_yI#7aC=O^vt2nk81$BCoHm; zHsxe9%%gBTFQ%i6+SsZ#)JSY7bv2m7(hr|a9U#df-((C+1`KR36cTrGhmkjH~3uNW%-lZ_bKTr!IAdP7V7pADq~Kdsh2V(X_@fo&Y>PD?nrDkbqjJ5oU?I zF=YBor(F(nCHx9t^^_XL%Td-au!9>$l=ZPs+O!*_k-R8p7;&qK<=AUH`xS{YKK2lo z>$xY2rpE6y%4=$QTWuT9)$gO(iu$Cp6%~|Pt+}ICjAu=)twjsfhSo+A3NR70C||Eu zDm8US*;b?Z?PjxWbhc~dX5(oz*H-JTn$lLaXmLl|s>b@YYFUYMCYq}#Z6$WAMOTzg z`a7Njyw`WRJfuvVR!mzIHqq+T*#YB=mmcL2AO}`P}ii=`4mSz6Ic_A&JR6I5QAufw3 HPVUUVr0Nj2 literal 0 HcmV?d00001 diff --git a/codingchallenge/__pycache__/urls.cpython-38.pyc b/codingchallenge/__pycache__/urls.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3c72db96e8003aa4474ea01268f765cf24de56d GIT binary patch literal 909 zcma)5L2uJA6i(8#D_I8;cO>7^NF`=)s|FgHn3#k(0K4>(xv`t1itWgD*vge(!(Ymk z6F&eUae-$yl_DghS;~{0Jb&-|zV~u{auRv4em&l0KLgMEZV$sXg2OpH`WXc3tvu?} zp!RvN3VgdC)gvCRLf>N{4Oy@qZzo@a!e1TH@kcM2kUId)C9On}m!z(lC>Xl_@D5!_ zv8jqy5mQMKMW0HBZdz40#2BW8zCm@R4N|f-Oxp_@8Hqku>{h4fQYyryVyF@usknnc zP0fgANHeyK5UkReMlUg@GS?~H5>d!T$t}xGnoEwEz%SBMY-CgA8rx(H!)iFMhB?>VD$pLA%jOB%^j0qScJ14dTEh<{``m>y<=$-9wJb0g zc&HTH7Y;X0lIRDFte5!Rm{48`*o4;cFB5mf?~`eFlw~jvhKi zp343RU8pO=y3i8ujt9;B=BWqp-G$#zFbq7iVL0_}ywYaZP0zTbZOvZ!a6?ZagpX$a IY&84x7u=#6F#rGn literal 0 HcmV?d00001 diff --git a/codingchallenge/__pycache__/wsgi.cpython-38.pyc b/codingchallenge/__pycache__/wsgi.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dc81b40f2aed838c506bd259a8c72782b6402ed GIT binary patch literal 543 zcmZ8f!D<^Z5Y@^~>~51%N(i|WbTzcAl-^1yEhIG#jdO59!53Lwjn^_-X;~>woNGU# z*XEdC(j|wU`UQoYGHVks6=N^typwG_V+LjE+)=4hky8K*x)qsW-3 zL{XEB6I@${maTCsA&|`5zqeFdZQWAzT0hBnThS5eZQtCd-$(rc%blI-f=ykGTcvH0 z&!jP63t(04=8%UhO$UL&b>%#G7G}WKiFi%^Fuq-tn2c3XUtNj9Zpq1HbZx=ILLQg$#R1+$$i6=1gqRi2Fw;UxxiNF*-g!9}N2= zae8)i@$LBL;6D{I4-K3YmRWxm`&wZ9c0xqc0aCl{e&NY_%53Evo77CFX$r?V5uGe literal 0 HcmV?d00001 diff --git a/codingchallenge/asgi.py b/codingchallenge/asgi.py new file mode 100644 index 0000000..31bd7ee --- /dev/null +++ b/codingchallenge/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for codingchallenge project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'codingchallenge.settings') + +application = get_asgi_application() diff --git a/codingchallenge/settings.py b/codingchallenge/settings.py new file mode 100644 index 0000000..04c0e86 --- /dev/null +++ b/codingchallenge/settings.py @@ -0,0 +1,122 @@ +""" +Django settings for codingchallenge project. + +Generated by 'django-admin startproject' using Django 3.1.2. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'p=e8@%i-com_+*m!b#wky%#ooql3m!a9re(=xx0e_g0c4hl(c_' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'api.apps.ApiConfig' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'codingchallenge.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'codingchallenge.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.1/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/codingchallenge/urls.py b/codingchallenge/urls.py new file mode 100644 index 0000000..5267f22 --- /dev/null +++ b/codingchallenge/urls.py @@ -0,0 +1,21 @@ +"""codingchallenge URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +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.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/codingchallenge/wsgi.py b/codingchallenge/wsgi.py new file mode 100644 index 0000000..aafc5a6 --- /dev/null +++ b/codingchallenge/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for codingchallenge project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'codingchallenge.settings') + +application = get_wsgi_application() diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1038f69 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" + +services: + migration: + build: . + command: python manage.py migrate --noinput + + web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - .:/code + - .db_data/db.sqlite3:/db.sqlite3 + ports: + - "8000:8000" + depends_on: + - migration diff --git a/instruction.md b/instruction.md new file mode 100644 index 0000000..9485a6a --- /dev/null +++ b/instruction.md @@ -0,0 +1 @@ +run docker-compose up in this directory \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..b143a22 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'codingchallenge.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d6970e8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Django>=3.0,<4.0 +psycopg2-binary>=2.8 +djangorestframework From e9042d1677a06d26c7333cf12918cc7014e83816 Mon Sep 17 00:00:00 2001 From: Muhammad Rofi Date: Sun, 4 Oct 2020 07:24:01 +0700 Subject: [PATCH 2/5] Add migrations, migrate db, and setup admin --- api/__pycache__/admin.cpython-38.pyc | Bin 153 -> 225 bytes api/__pycache__/models.cpython-38.pyc | Bin 150 -> 643 bytes api/admin.py | 2 ++ api/migrations/0001_initial.py | 28 ++++++++++++++++++ .../__pycache__/0001_initial.cpython-38.pyc | Bin 0 -> 1014 bytes api/models.py | 7 +++++ .../__pycache__/settings.cpython-38.pyc | Bin 2327 -> 2327 bytes db.sqlite3 | Bin 0 -> 139264 bytes instruction.md | 2 +- 9 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 api/migrations/0001_initial.py create mode 100644 api/migrations/__pycache__/0001_initial.cpython-38.pyc diff --git a/api/__pycache__/admin.cpython-38.pyc b/api/__pycache__/admin.cpython-38.pyc index 04dd803c5413be76be784fba2b43501d00ed407e..3e208172b70f9734f495b987215247aa453dd9f1 100644 GIT binary patch literal 225 zcmWIL<>g`kg0rHP@zp^3F^GcP(&AaUkk22GZiK-CPIjJH@5Q*tx&fDHD4qWrYXoK!zewkW=oti-(Ze7)rS zypp2Kq#`Du+9K{FKz3;v$fxw^)iZOHyxf6s4wT7MG+Jtz;--0}6wQUqbrH jK$ZGHE&5;!^a?6(ao7NLm*%9}F#?qpvj7PmMjmDWp87N1 literal 153 zcmWIL<>g`kf>PniczYoI7{oyaOhAqU5Elyoi4=wu#vF!R#wbQch7_h?22JLdKv4!w z##^k3DY=<>ews{Cd?{IpdFlCj$@zIDMVU!OAOlu16fpxSF!4)BKRG`oRX?#HQy;8J Yub}c4hfQvNN@-529V1ZLXCP((0F4+QegFUf diff --git a/api/__pycache__/models.cpython-38.pyc b/api/__pycache__/models.cpython-38.pyc index e65469d2e8119a8841decac5c44a045123e2764a..6ad59d3dd8d599b82ce76798c668cfdb3906fb29 100644 GIT binary patch literal 643 zcmY*XJ#y4A6qfvNW2XSpT#jxtI;4ph*EETrTO zfe@na1R_bd(YKJr5MPlx`HeoLrY`9WNYx{Xu6t?M-uzf%B&T@#OH`6D3WNnR+(t4+ zodAVkAF>p2<`Y4@4_N*mE08_Gn`BQ}dB7>+3NkDtcjGJGqd3OHFMI+A0<;#t*(=|)PXE6xt z;=z2B+EwRR>ZnGp) z7m(4X^a#I_PWFpBaXD;-zBLWp%tfCZ_}b{fwl@uSzG>!ga|iKTzvjvRU+x(lV+xnO Qm}l}59~$X>`jVF6U)tEFJOBUy literal 150 zcmWIL<>g`kf>PnicuOGt7{oyaOhAqU5Elyoi4=wu#vF!R#wbQch7_h?22JLdKv4!w z##?N;`6;P6#eSMhw>VR>67$mY^-_|GK$=%F6tMs)F!4)RKN+Y_Kd~TFAEH68pz;=n R4Uk=$lWNBZRQDN(82~WZ9>M?s diff --git a/api/admin.py b/api/admin.py index 8c38f3f..535c8ca 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin +from api.models import Profile # Register your models here. +admin.site.register(Profile) diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..4425e45 --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.2 on 2020-10-04 00:18 + +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='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('avatar', models.CharField(max_length=1024, null=True)), + ('token', models.CharField(max_length=1024, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/api/migrations/__pycache__/0001_initial.cpython-38.pyc b/api/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf8bf72be10f6d86d435060ee276e72038099b7c GIT binary patch literal 1014 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 K2^` Date: Sun, 4 Oct 2020 09:01:45 +0700 Subject: [PATCH 3/5] Create serializers and views --- api/__pycache__/admin.cpython-38.pyc | Bin 225 -> 225 bytes api/serializers.py | 36 +++++++++ api/views.py | 113 +++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 api/serializers.py diff --git a/api/__pycache__/admin.cpython-38.pyc b/api/__pycache__/admin.cpython-38.pyc index 3e208172b70f9734f495b987215247aa453dd9f1..758499418dfa03272f69b5f260ae048dc8450e1b 100644 GIT binary patch delta 18 YcmaFJ_>hq+l$V!_0SNADPvp7|04y5>!Thq+l$V!_0SL~DPUN}{04s9@oB#j- diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..e196e7a --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,36 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from api.models import Profile +from django.utils.crypto import get_random_string + +class UserSerializer(serializers.ModelSerializer): + avatar = serializers.SerializerMethodField('get_avatar') + + def get_avatar(self, foo): + try: + return Profile.objects.get(user=foo.id).avatar + except Profile.DoesNotExist: + return None + + class Meta: + model = User + fields = ['id', 'email', 'first_name', 'last_name', 'avatar'] + + +class RegisterSerializer(serializers.ModelSerializer): + token = serializers.SerializerMethodField('get_token') + + def get_token(self, foo): + try: + return Profile.objects.get(user=foo.id).token + except Profile.DoesNotExist: + return None + + def create(self, validated_data): + user = User.objects.create(**validated_data) + Profile.objects.create(user=user, token=get_random_string(length=18)) + return user + + class Meta: + model = User + fields = ['id', 'email', 'password', 'username', 'token'] diff --git a/api/views.py b/api/views.py index 91ea44a..31b5953 100644 --- a/api/views.py +++ b/api/views.py @@ -1,3 +1,116 @@ from django.shortcuts import render +from django.http import HttpResponse, JsonResponse +from django.views.decorators.csrf import csrf_exempt +from rest_framework.parsers import JSONParser +from django.contrib.auth.models import User +from api.models import Profile +from api.serializers import UserSerializer, RegisterSerializer +from django.core.paginator import Paginator +import time + # Create your views here. +@csrf_exempt +def user_list(request): + if request.method == 'GET': + page_size = 6 + page_number = 1 + + if 'page' in request.GET: + page_number = request.GET['page'] + + if 'delay' in request.GET: + delay = request.GET['delay'] + time.sleep(int(delay)) + + users = User.objects.all() + paginator = Paginator(users, page_size) + + if int(page_number) > paginator.num_pages: + return HttpResponse(status=404) + + paginated_users = paginator.page(page_number).object_list + + serializer = UserSerializer(paginated_users, many=True) + Response = { + "page": page_number, + "per_page": page_size, + "total": users.count(), + "total_pages": paginator.num_pages, + "data": serializer.data + } + return JsonResponse(Response, safe=False) + + +@csrf_exempt +def user_detail(request, pk): + try: + user = User.objects.get(pk=pk) + except User.DoesNotExist: + return HttpResponse(status=404) + + if request.method == 'GET': + serializer = UserSerializer(user) + return JsonResponse(serializer.data) + elif request.method == 'PUT' or request.method == 'PATCH': + data = JSONParser().parse(request) + if 'avatar' in data: + avatar = data.pop('avatar') + try: + profile = Profile.objects.get(user=pk) + profile.avatar = avatar + profile.save() + except Profile.DoesNotExist: + Profile.objects.create(user=user, avatar=avatar) + elif 'token' in data: + token = data.pop('token') + try: + profile = Profile.objects.get(user=pk) + profile.token = token + profile.save() + except Profile.DoesNotExist: + Profile.objects.create(user=user, avatar=avatar) + + serializer = UserSerializer(user, data=data) + if serializer.is_valid(): + serializer.save() + return JsonResponse(serializer.data) + return JsonResponse(serializer.errors, status=400) + + elif request.method == 'DELETE': + user.delete() + return HttpResponse(status=204) + + +@csrf_exempt +def register(request): + if request.method == 'POST': + data = JSONParser().parse(request) + username = data['email'].split('@')[0] + data['username'] = username + + serializer = RegisterSerializer(data=data) + if serializer.is_valid(): + serializer.save() + Response = { + "id": serializer.data['id'], + "token": serializer.data['token'] + } + return JsonResponse(Response) + return HttpResponse(status=404) + + +@csrf_exempt +def login(request): + if request.method == 'POST': + data = JSONParser().parse(request) + + try: + user = User.objects.get(email=data['email'], password=data['password']) + profile = Profile.objects.get(user=user) + Response = { + "token": profile.token + } + return JsonResponse(Response) + except: + return HttpResponse(status=404) \ No newline at end of file From c2c9bec8a9853e485532d1489859501a92291b42 Mon Sep 17 00:00:00 2001 From: Muhammad Rofi Date: Sun, 4 Oct 2020 12:08:52 +0700 Subject: [PATCH 4/5] Create urls, update views, create tests and add more data --- api/__pycache__/serializers.cpython-38.pyc | Bin 0 -> 1833 bytes api/__pycache__/tests.cpython-38.pyc | Bin 0 -> 2727 bytes api/__pycache__/urls.cpython-38.pyc | Bin 0 -> 464 bytes api/__pycache__/views.cpython-38.pyc | Bin 0 -> 2800 bytes api/tests.py | 66 ++++++++++++++++++ api/urls.py | 13 ++++ api/views.py | 2 +- .../__pycache__/urls.cpython-38.pyc | Bin 909 -> 1030 bytes codingchallenge/urls.py | 5 +- db.sqlite3 | Bin 139264 -> 139264 bytes 10 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 api/__pycache__/serializers.cpython-38.pyc create mode 100644 api/__pycache__/tests.cpython-38.pyc create mode 100644 api/__pycache__/urls.cpython-38.pyc create mode 100644 api/__pycache__/views.cpython-38.pyc create mode 100644 api/urls.py diff --git a/api/__pycache__/serializers.cpython-38.pyc b/api/__pycache__/serializers.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ff5d411b617961838a941dbef9dbac8a84a1ea2 GIT binary patch literal 1833 zcmb_cOK%)S5bo}I?%LiYw#fqph~UCX)>=Xw5wgVVloi5?2y+=twtLsJp4~CkvjJP? zlw8ekfTMp0|Dvy)@)tPqRgaxrKXO2#M_toh{iv$1zUpTi8(o3+*UoW13WfNaC#wxQ zv4?J-LujF8C1x^{lE8l{?K$GYc zn)E``)t!CO@4mt9qA%T=HB=T=d1A<78a~4u|KxkB$7N+Oyf-mTPQ_f;v)ndR&L<;2 zyV?Rw_R#GPgb|t4B2y;#Bh>OwnMGRZ;I+tN_!8;}zC^E7mgpFMB(BF6_uo3=sAmE^fR8ad31@36?wPmK_#S$!&R8o;0l*L-nQBzO!m<2hY(9ZGXTn9 zGK>=>zlHE*>)ORn4ZvH+Nf((}QC6-yF3C3eyqFo+sfvpsZD7mrJa%dBm-5_o^L$q8 zMP)Ev&-0%bMa75moH)fU!lJ2MJT6V8tw&)+Z1R?njo-n@GQOz{B|XMq*iY#J{`8Q= zBNm)c_uew|ICxO&Z~1EPnMctdUa`Tz6C0u!g1cpr%C1UduX{H+KgY%;meO(XCzj;bbn+t{#zZSH{qRptcNIh0C%q!S< zSU!S|fxb$;$2a}=^tNmKP+4>KH?$W$MQ;g zmLYIKe9R$SE0NA^B|;pmzmYh%#PPqoD)_zFM)&D&t0IkvwZs0J>ybh2j|o1)Ar)PArndK|wP!!BcWO?6Lqb@f+YSNWh;s~GV7_RanHchLU8 z&f;T3X9t@276dgY$&3Ms2tnP<%z+hITDLNL;6#qr?aUo`k*9Sh^9Ml`Xx+^!gD?t- z@tHwA>OVB7&%6g#RHeb!MyqlGI~XmZ{Cy_m-9#|ZJJD!|QfbI~PW?eamG9B6l;iSIy2fa(D`hD~sTU(I84Vx%NBCuCycRwvf6 zH8D=DiIv*YN!>G(7}%Fy>YoN@0MOD%<;+M!YT+0)z!ATre9n4(AsK&EOX#3K+!-YO ztliC(qr2y(au^ONf0PLEbIz%N7ILTC4ARl*g$~c}>g`e;5@5Q&ax8vO}xr-xSelKNRDU{RA$6&_lF{%797#Su5 zc5W+Mu}+78~7C=Da1>lsg3319713j zMO&f@LgQeVf7upbaC037#iI2P6ud>2;^ly5od8E;OWK;bMhZ+mAu!@dmpW5+6Y5Uc z4b0W-hO0e=Dgbs@WSCZGybU@$1a~z`=N7+?x`wR|8ZgsF#waJtd;KBDlPy45(@f*n zU=-NOSpwXD1E+X6;*E;i|L=J7XK_Ipz)cOX-uRMc&%xX&%{G8sbE+NPSb=zH*?NF6 zhf19W9vfhnno}k|DZKnOJnHKxS|~Kkb7C%_$=}59Gbs66*u|uvJQ;|Pc{O;{rQV6o#HYc;Tw$iaz|1*xQGkSuGf1@M zO#Bb*HW8=gE3YZovN*Df-$d~Oik16b*Er5Le-EbUv}cfliVbpfZ3VvBjU>eU=h;gz$bp_ z`_Mq@FP&CRg)~iuy51AF+dAgVwyzZ}3Ut(__JU-@?mC_W@GO&_94WKd+{HYe(pnZL(FF?x@c zc+$pKqO5c;l(m|o7Gv;R$&4r4vT8;?1;jxMrA=VNLE@{CPnB#;e&Vib`it&iZ^G#c zh`y-gxLKOnRT`KOjzv?p+d)aK@HdHJMOP_?nR2agE54Ng1sf`fgX5fyekCflhLx|g i?5+io;p5Eq+IA=7@9Gu?DfSdE=yQ~aMl7L={rL?{ymgBJ literal 0 HcmV?d00001 diff --git a/api/__pycache__/views.cpython-38.pyc b/api/__pycache__/views.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fadc203093692b60e4c32c4bd612f9afe6a5253 GIT binary patch literal 2800 zcmaJ@&2A&d5uWb(<&YvNt}X96MzV*%Hf=|X_y;hIg|UuT&SnwFf~AGmb1*StwZ_`G zw6+#I_}zPaI(Xc6oPX2e`p?DU9)|uKh;RhUoSY?$5zjN8yNR27iD%bt=I22YSl-LR zJW3+V`&lE8li2b>*34T;%kp8imba63-bp%k9cA5oJz2MWBkLtS8HiXkzjn@9aszxz ztXcj!@NLnt{PW9Z8>)(KaC7ktiyJLkfKj7>yinq$339?jF~Y2Q~J(vN0R4eB$d zFlm-F?PNu|@<1{tELC_a@xERJ#CO;Yhd`i&}7@{41sP1PWC z2O6QTnwG_t`%onay5opd(3 zb25@&>do0{)u40MxHYKg*FYQ|Gmm+oF%8z{U2tuT7XF-Z7SR5Gf>8Yq+CxrfR=)@{ z4GX}p5uUT20nEk-x?mS<&Oddgw><|kcM_{NnL~f$2`=0V7C4_F@Q(?!zVHCr9TO0^ z0LHL%$I&^#iuU{o*9VphCj1+w_D$p5S;VD7&=g_m0p9-t$N)x2{IlDpwXy-woCl>} z27|RSShSaTIxKn_7PYKJ|6Ex_`W-;k7mW+fVAqsCcBY?;nC`|C->|pGxIO&I?ICXO zUc3F(tJ`tzwO_iQ-nR5a6KUd>?qYrE;KUv{Yv(f`Sgwa;aVc4>AJ(iN{!;SA4SFYd zv`rperBY`bFz2(0>LE1h1`!K#cjwVwG&vMS8ynseDsR zqqCuvWqStv_$=ZC!qc{r+<{In=L7*FBV`YTYlS-Y(46SA=?-3@_4cgnx_q z8vT^ol|Q3$Sh=%erU*LqBaxIRvI>Su0^RCuQjA7=aDuWZssI^LAQJ+V0%>GFQop5x zn?$VDed26_S^L9TU0>}zHWzC023^~<86c!dM;YN+{{h4S&U>uOy2yYCIk3T6cIK=N z^tZ5c6Ve#DP_KJzgSW6I-8Q(0DZ1uI#o32|Tw!zL1Pmfq7iVP{@XIZJ88U_6au%*| zm#gwjB~$o!5mn3q5KQRsmySBZ98kQG9ia$s)pK;q5shy-igZ+ZlrsjU7pS^ILuqN-$fWlM$8G`z;vLU4ys`o%pEN9usC~EoY zeIj>>JS}q;1}M!oR95;C?b}dM+4=vtL~7Of9${0{0V+%_ge9^;iFm}K{{|jh{Q!GE zaWxrcU7dk>>guEGu0DIuxMOeZ*DeQj;W`iL$4y}6=#1oU5s-sN)c34QKSh~ZC32(g zcUPs$cCN;@q6e${AZS9V)?cDgz>OqOe&?*kGtv5idF9GtKfGc;rA3Qe(u+>BC^j}E2Vw_G7i%or0C^I}ZT2Ya& znk(@UzIf`aCQGW9R^qy;2stbieVXV^`u0%-`(#6*yA2K$&;?&iHf_!;t!xE&adm!J zpe>H}H`8Ntw3(w-W!m~6iP5sI1muOJLxN|mPS=HnkXJqb^fD#!nAK;JZP4mhP_RAA uCZ~msedBA;qeiK0U5M_&gyT%UPsyQggHYPHR)69mkF)qU7_VTgbM`+?Qhfse literal 0 HcmV?d00001 diff --git a/api/tests.py b/api/tests.py index 7ce503c..d1f6225 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,3 +1,69 @@ from django.test import TestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase +from django.contrib.auth.models import User +from api.models import Profile # Create your tests here. +class UserTests(APITestCase): + def test_register_success(self): + url = reverse('register') + data = { + "email": "admin@mail.co", + "password": "admin" + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(User.objects.count(), 1) + self.assertEqual(User.objects.get().username, 'admin') + + def test_register_failed(self): + url = reverse('register') + data = { + "email": "admin@mail.co" + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_login_success(self): + self.user = User.objects.create(username='admin', email='admin@mail.co', password='admin') + self.profile = Profile.objects.create(user=self.user) + url = reverse('login') + data = { + "email": "admin@mail.co", + "password": "admin" + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_login_failed(self): + self.user = User.objects.create(username='admin', email='admin@mail.co', password='admin') + self.profile = Profile.objects.create(user=self.user) + url = reverse('login') + data = { + "email": "admin@mail.co" + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_user_list(self): + self.user = User.objects.create(username='admin', email='admin@mail.co', password='admin') + self.profile = Profile.objects.create(user=self.user) + url = reverse('user_list') + + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_user_detail(self): + self.user = User.objects.create(username='admin', email='admin@mail.co', password='admin') + self.profile = Profile.objects.create(user=self.user) + + url = reverse('user_detail', args=[1]) + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + url_2 = reverse('user_detail', args=[2]) + response = self.client.get(url_2, format='json') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) \ No newline at end of file diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..460b14c --- /dev/null +++ b/api/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from api import views + +urlpatterns = [ + path('register', views.register, name='register'), + path('register/', views.register, name='register'), + path('login', views.login, name='login'), + path('login/', views.login, name='login'), + path('users', views.user_list, name='user_list'), + path('users/', views.user_list, name='user_list'), + path('users/', views.user_detail, name='user_detail'), + path('users//', views.user_detail, name='user_detail'), +] diff --git a/api/views.py b/api/views.py index 31b5953..40ec3f9 100644 --- a/api/views.py +++ b/api/views.py @@ -23,7 +23,7 @@ def user_list(request): delay = request.GET['delay'] time.sleep(int(delay)) - users = User.objects.all() + users = User.objects.all().order_by('id') paginator = Paginator(users, page_size) if int(page_number) > paginator.num_pages: diff --git a/codingchallenge/__pycache__/urls.cpython-38.pyc b/codingchallenge/__pycache__/urls.cpython-38.pyc index a3c72db96e8003aa4474ea01268f765cf24de56d..3f829d6bf5d69a7f775999d725fd04435fa4a2b9 100644 GIT binary patch delta 267 zcmeBWZ{yGo<>lpK0D=QSmGK$O3=EG!9Av--S!QZ^aS_m|47a$ULLhZTEI^SWR**1%N@`kSX--KzP&_$5FKx0OvkV(A cNSb$Y0JEVGACSqy&%?;WD8S4GBzgV<0CHSFXaE2J delta 146 zcmZqU=w;Uq<>lpK0D|vAmGNJh7#JRdILLq*$Z-JTVuOj=@?xnhDJ-c>S*%%X%}nWx zQS2$K!3>&g8{7Jr1Xv0ZOERk15>s+B^Yr~RIVO8B%L(1$PRUBlOV8IUEy^h_0+}^= dA+rn{Cy-IZIr%WNArA+T$->XW$ivL@9{>_J9qa%A diff --git a/codingchallenge/urls.py b/codingchallenge/urls.py index 5267f22..11b767f 100644 --- a/codingchallenge/urls.py +++ b/codingchallenge/urls.py @@ -14,8 +14,11 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.views import debug +from django.urls import path, include urlpatterns = [ + path('', debug.default_urlconf), path('admin/', admin.site.urls), + path('api/', include('api.urls')) ] diff --git a/db.sqlite3 b/db.sqlite3 index 8174ca5412cab15cfc46ddcdb3b812b3e4ad9b74..e459ed768aa8da4eb619f2fe92183b4818c8755f 100644 GIT binary patch delta 2077 zcmZ{lPiP}m9LHxe`7_B(@^(d$ZVS7j7g=`p{h9Y>Ca1K?Zj()S+R}Du>&kBawMl97 zr`y!nWtkQa9t5crJPD!)5%FMIWDk4TB6#w$f~Xfk@gxX(5n;VJGnq^^)8z1-AAIxs zDhmWro;Djtc;J|HgA@yVG z{nTRWx#ZL2yUCekO#VuKaD4E(Y-MGc8|TFFO5YrC~zmz`TgG?k8UbYvt<(c>+d zjpC}MujcD3%X`JywS}G@Xme6b?2Cs-Ih4fBmf}&>F1Kyfj=@rezX1k&r{lYT;& zrk?Fs_K%^6DR{vA+$L)m%6odbuveIGt}L$)we^PqCcz#1j$wq&(BK4NyZ%N|yH?p- zTG?67np2G-o@KY$qd_A?9lq7Rqcg$eJ>b-}8=E;YjYu`0D-{cGR;oilmUadOW4ca8 zIEG<_682kG0#7*b!av|I@Q3}L{uTTLegNOvQ{-##NEx$z}Q~ ziadPG@|=mKB^mLh#i>OM3=G0Rj0`sK*7r1EW8{xv;E&nZ7{ot0CVvHx`<;RR`)0w0 fxBSiD>$iWeXXFjo%<|xm{G Date: Sun, 4 Oct 2020 12:11:00 +0700 Subject: [PATCH 5/5] Update instructions --- instruction.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/instruction.md b/instruction.md index bce104f..996eb2a 100644 --- a/instruction.md +++ b/instruction.md @@ -1 +1,4 @@ -run "docker-compose up" in this directory \ No newline at end of file +To run this application: +1. Run "docker-compose up" in this directory +2. Check the api with the same documentation with https://reqres.in/ +3. Example, to get all users http://0.0.0.0:8000/api/users or http://0.0.0.0:8000/api/users?page=2 \ No newline at end of file