From 1b3a4c1ece518f4a319f78d2af82438f42d4be76 Mon Sep 17 00:00:00 2001 From: Jonathanjojo Date: Mon, 24 Feb 2020 14:47:55 +0700 Subject: [PATCH 1/7] Added accounts app and model --- apps/accounts/__init__.py | 0 apps/accounts/admin.py | 3 ++ apps/accounts/apps.py | 5 ++++ apps/accounts/migrations/0001_initial.py | 29 +++++++++++++++++++ apps/accounts/migrations/__init__.py | 0 apps/accounts/models.py | 10 +++++++ apps/accounts/tests/__init__.py | 0 apps/accounts/tests/factories/__init__.py | 0 apps/accounts/tests/factories/accounts.py | 0 apps/accounts/tests/test_units/__init__.py | 0 .../tests/test_units/test_accounts.py | 0 apps/accounts/urls.py | 21 ++++++++++++++ apps/accounts/views.py | 3 ++ project/settings.py | 1 + project/urls.py | 3 +- 15 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 apps/accounts/__init__.py create mode 100644 apps/accounts/admin.py create mode 100644 apps/accounts/apps.py create mode 100644 apps/accounts/migrations/0001_initial.py create mode 100644 apps/accounts/migrations/__init__.py create mode 100644 apps/accounts/models.py create mode 100644 apps/accounts/tests/__init__.py create mode 100644 apps/accounts/tests/factories/__init__.py create mode 100644 apps/accounts/tests/factories/accounts.py create mode 100644 apps/accounts/tests/test_units/__init__.py create mode 100644 apps/accounts/tests/test_units/test_accounts.py create mode 100644 apps/accounts/urls.py create mode 100644 apps/accounts/views.py diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/accounts/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py new file mode 100644 index 0000000..9b3fc5a --- /dev/null +++ b/apps/accounts/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'accounts' diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..79a581e --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 3.0.1 on 2020-02-24 07:32 + +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='Account', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128)), + ('email', models.EmailField(max_length=128)), + ('phone_number', models.CharField(max_length=16)), + ('area', models.CharField(max_length=128)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('is_admin', models.BooleanField(default=False)), + ], + ), + ] diff --git a/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/models.py b/apps/accounts/models.py new file mode 100644 index 0000000..39cc41f --- /dev/null +++ b/apps/accounts/models.py @@ -0,0 +1,10 @@ +from django.contrib.auth.models import User +from django.db import models + +class Account(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + name = models.CharField(max_length=128) + email = models.EmailField(max_length=128) + phone_number = models.CharField(max_length=16) + area = models.CharField(max_length=128) + is_admin = models.BooleanField(default=False) diff --git a/apps/accounts/tests/__init__.py b/apps/accounts/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/tests/factories/__init__.py b/apps/accounts/tests/factories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/tests/test_units/__init__.py b/apps/accounts/tests/test_units/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/tests/test_units/test_accounts.py b/apps/accounts/tests/test_units/test_accounts.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py new file mode 100644 index 0000000..8d04be9 --- /dev/null +++ b/apps/accounts/urls.py @@ -0,0 +1,21 @@ +"""app URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/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, include + +urlpatterns = [ + path('auth/', include('django.contrib.auth.urls')), +] diff --git a/apps/accounts/views.py b/apps/accounts/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/accounts/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/project/settings.py b/project/settings.py index 6918f04..b5a80ff 100644 --- a/project/settings.py +++ b/project/settings.py @@ -42,6 +42,7 @@ INSTALLED_APPS = [ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", + "apps.accounts", ] MIDDLEWARE = [ diff --git a/project/urls.py b/project/urls.py index eb67583..35f2a4d 100644 --- a/project/urls.py +++ b/project/urls.py @@ -17,5 +17,6 @@ from django.contrib import admin from django.urls import path, include urlpatterns = [ - path("admin/", admin.site.urls), + path('admin/', admin.site.urls), + path('accounts/', include('apps.accounts.urls')), ] -- GitLab From 58e8973b2babad8ca39259170d51984ebf239ae9 Mon Sep 17 00:00:00 2001 From: Jonathanjojo Date: Mon, 24 Feb 2020 16:49:44 +0700 Subject: [PATCH 2/7] [RED] Added tests and extra flags for account app --- apps/accounts/migrations/0001_initial.py | 9 +- apps/accounts/models.py | 6 + apps/accounts/tests/factories/accounts.py | 32 +++++ .../tests/test_units/test_accounts.py | 114 ++++++++++++++++++ 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index 79a581e..c2944c5 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -1,8 +1,9 @@ -# Generated by Django 3.0.1 on 2020-02-24 07:32 +# Generated by Django 3.0.1 on 2020-02-24 09:13 from django.conf import settings from django.db import migrations, models import django.db.models.deletion +import uuid class Migration(migrations.Migration): @@ -17,13 +18,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Account', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128)), ('email', models.EmailField(max_length=128)), ('phone_number', models.CharField(max_length=16)), ('area', models.CharField(max_length=128)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('is_admin', models.BooleanField(default=False)), + ('is_verified', models.BooleanField(default=False)), + ('is_active', models.BooleanField(default=False)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/apps/accounts/models.py b/apps/accounts/models.py index 39cc41f..a37e81f 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -1,10 +1,16 @@ +import uuid + from django.contrib.auth.models import User from django.db import models + class Account(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField(User, on_delete=models.CASCADE) name = models.CharField(max_length=128) email = models.EmailField(max_length=128) phone_number = models.CharField(max_length=16) area = models.CharField(max_length=128) is_admin = models.BooleanField(default=False) + is_verified = models.BooleanField(default=False) + is_active = models.BooleanField(default=False) diff --git a/apps/accounts/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py index e69de29..4441a3d 100644 --- a/apps/accounts/tests/factories/accounts.py +++ b/apps/accounts/tests/factories/accounts.py @@ -0,0 +1,32 @@ +from apps.accounts.models import Account + +from django.contrib.auth.models import User +from faker import Faker +import factory + + +faker = Faker() + + +class UserFactory(factory.DjangoModelFactory): + class Meta: + model = User + + username = 'username' + password = 'justpass' + + +class AccountFactory(factory.DjangoModelFactory): + class Meta: + model = Account + + class Params: + admin = factory.Trait( + is_admin=True, + ) + + user = factory.SubFactory(UserFactory) + name = faker.name() + email = faker.email() + phone_number = faker.phone_number() + area = faker.city() diff --git a/apps/accounts/tests/test_units/test_accounts.py b/apps/accounts/tests/test_units/test_accounts.py index e69de29..70ec3db 100644 --- a/apps/accounts/tests/test_units/test_accounts.py +++ b/apps/accounts/tests/test_units/test_accounts.py @@ -0,0 +1,114 @@ +from django.test import TestCase +from django.urls import reverse +from faker import Faker +from rest_framework import status + + +from apps.accounts.models import Account +from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory + + +class AccountViewTest(TestCase): + def setUp(self): + self.user_1 = UserFactory(username='user_1') + self.user_2 = UserFactory(username='user_2') + self.admin = AccountFactory(admin=True, user=self.user_1) + self.officer = AccountFactory(admin=False, user=self.user_2) + self.accounts = [self.admin, self.officer] + + self.faker = Faker() + + def test_list_all_accounts(self): + url = '/accounts/' + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + for account in self.accounts: + self.assertContains(response, account.name) + self.assertContains(response, account.user.username) + self.assertContains(response, account.email) + self.assertContains(response, account.phone_number) + self.assertContains(response, account.area) + self.assertContains(response, account.is_admin) + self.assertContains(response, account.is_active) + self.assertContains(response, account.is_verified) + + def test_create_new_admin(self): + url = '/accounts/' + + _account_id = self.faker.email() + admin_prev_count = Account.objects.filter(is_admin=True).count() + + data = { + 'name': self.faker.name(), + 'username': _account_id, + 'password': 'justpass', + 'email': _account_id, + 'phone_number': self.faker.phone_number(), + 'area': self.faker.city(), + 'is_admin': True, + } + + response = self.client.post( + url, + data, + format='json' + ) + admin_current_count = Account.objects.filter(is_admin=True).count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(admin_current_count, admin_prev_count + 1) + + + def test_create_new_officer(self): + url = '/accounts/' + + _account_id = self.faker.email() + officer_prev_count = Account.objects.filter(is_admin=False).count() + + data = { + 'name': self.faker.name(), + 'username': _account_id, + 'password': 'justpass', + 'email': _account_id, + 'phone_number': self.faker.phone_number(), + 'area': self.faker.city(), + 'is_admin': False, + } + + response = self.client.post( + url, + data, + format='json' + ) + officer_current_count = Account.objects.filter(is_admin=False).count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(officer_current_count, officer_prev_count + 1) + + + def test_edit_account(self): + url = '/accounts/' + str(self.officer.id) + '/' + + data = { + 'id': self.officer.id, + 'name': self.faker.name(), + 'email': self.faker.email(), + 'phone_number': self.faker.phone_number(), + 'area': self.faker.city(), + 'is_admin': False, + 'is_verified': True, + 'is_active': True, + } + + response = self.client.put( + url, + data, + format='json' + ) + + self.assertJSONEqual( + str(response.content, encoding='utf8'), + data + ) -- GitLab From 29f5024fa75d43d26237ffeb85b8250c1bc1c815 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Mon, 24 Feb 2020 17:02:37 +0700 Subject: [PATCH 3/7] [CHORES] Fix test source directory on CI script --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e84525..a3fd8e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,7 +39,7 @@ test: script: - echo "Starting tests" - coverage erase - - coverage run --include="./*/*" --omit="./env/*","./manage.py" manage.py test + - coverage run --include="./*/*" --omit="./env/*","./manage.py" manage.py test apps - coverage xml -i - coverage report -m artifacts: -- GitLab From ba69f8692d2d7cc337934939f82177e6a1926731 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 25 Feb 2020 00:28:40 +0700 Subject: [PATCH 4/7] [GREEN] Implemented viewsets and fixed wrong test fixtures --- apps/__init__.py | 0 apps/accounts/migrations/0001_initial.py | 2 +- apps/accounts/models.py | 2 +- apps/accounts/serializers.py | 44 ++++++++++++++++ .../tests/test_units/test_accounts.py | 40 +++++++------- apps/accounts/urls.py | 9 ++++ apps/accounts/views.py | 52 ++++++++++++++++++- project/settings.py | 2 +- 8 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 apps/__init__.py create mode 100644 apps/accounts/serializers.py diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index c2944c5..afe3f72 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128)), ('email', models.EmailField(max_length=128)), - ('phone_number', models.CharField(max_length=16)), + ('phone_number', models.CharField(max_length=64)), ('area', models.CharField(max_length=128)), ('is_admin', models.BooleanField(default=False)), ('is_verified', models.BooleanField(default=False)), diff --git a/apps/accounts/models.py b/apps/accounts/models.py index a37e81f..9d67a2f 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -9,7 +9,7 @@ class Account(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) name = models.CharField(max_length=128) email = models.EmailField(max_length=128) - phone_number = models.CharField(max_length=16) + phone_number = models.CharField(max_length=64) area = models.CharField(max_length=128) is_admin = models.BooleanField(default=False) is_verified = models.BooleanField(default=False) diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py new file mode 100644 index 0000000..6cd91c0 --- /dev/null +++ b/apps/accounts/serializers.py @@ -0,0 +1,44 @@ +from rest_framework import serializers +from apps.accounts.models import Account + +class AccountSummarySerializer(serializers.ModelSerializer): + username = serializers.CharField(source='user.username', required=False) + + class Meta: + model = Account + fields = [ + 'id', + 'username', + 'name', + 'email', + 'phone_number', + 'area', + 'is_admin', + 'is_verified', + 'is_active', + ] + + read_only_fields = [ + 'id', + 'username', + ] + + +class AccountRegisterSerializer(serializers.ModelSerializer): + username = serializers.CharField(max_length=128) + password = serializers.CharField(max_length=128) + + class Meta: + model = Account + fields = [ + 'id', + 'username', + 'password', + 'name', + 'email', + 'phone_number', + 'area', + 'is_admin', + 'is_verified', + 'is_active', + ] diff --git a/apps/accounts/tests/test_units/test_accounts.py b/apps/accounts/tests/test_units/test_accounts.py index 70ec3db..83fd2f7 100644 --- a/apps/accounts/tests/test_units/test_accounts.py +++ b/apps/accounts/tests/test_units/test_accounts.py @@ -1,9 +1,9 @@ +import json from django.test import TestCase from django.urls import reverse from faker import Faker from rest_framework import status - from apps.accounts.models import Account from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory @@ -24,16 +24,6 @@ class AccountViewTest(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - for account in self.accounts: - self.assertContains(response, account.name) - self.assertContains(response, account.user.username) - self.assertContains(response, account.email) - self.assertContains(response, account.phone_number) - self.assertContains(response, account.area) - self.assertContains(response, account.is_admin) - self.assertContains(response, account.is_active) - self.assertContains(response, account.is_verified) - def test_create_new_admin(self): url = '/accounts/' @@ -51,9 +41,10 @@ class AccountViewTest(TestCase): } response = self.client.post( - url, - data, - format='json' + path=url, + data=data, + content_type='application/json', + format='json', ) admin_current_count = Account.objects.filter(is_admin=True).count() @@ -78,9 +69,10 @@ class AccountViewTest(TestCase): } response = self.client.post( - url, - data, - format='json' + path=url, + data=data, + content_type='application/json', + format='json', ) officer_current_count = Account.objects.filter(is_admin=False).count() @@ -92,7 +84,7 @@ class AccountViewTest(TestCase): url = '/accounts/' + str(self.officer.id) + '/' data = { - 'id': self.officer.id, + 'id': str(self.officer.id), 'name': self.faker.name(), 'email': self.faker.email(), 'phone_number': self.faker.phone_number(), @@ -103,12 +95,16 @@ class AccountViewTest(TestCase): } response = self.client.put( - url, - data, - format='json' + path=url, + data=data, + content_type='application/json', + format='json', ) + expected_returned_data = data + expected_returned_data['username'] = self.officer.user.username + self.assertJSONEqual( str(response.content, encoding='utf8'), - data + expected_returned_data ) diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index 8d04be9..b73f577 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -15,7 +15,16 @@ Including another URLconf """ from django.contrib import admin from django.urls import path, include +from rest_framework.routers import DefaultRouter + +from apps.accounts.views import AccountViewSet + + +router = DefaultRouter() +router.register(r'', AccountViewSet, basename='account') urlpatterns = [ path('auth/', include('django.contrib.auth.urls')), ] + +urlpatterns += router.urls diff --git a/apps/accounts/views.py b/apps/accounts/views.py index 91ea44a..a9490bd 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -1,3 +1,51 @@ -from django.shortcuts import render +from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 +from rest_framework import status, viewsets +from rest_framework.response import Response -# Create your views here. +from apps.accounts.models import Account +from apps.accounts.serializers import AccountSummarySerializer, AccountRegisterSerializer + + +class AccountViewSet(viewsets.ViewSet): + queryset = Account.objects.all().select_related('user') + + def list(self, request): + queryset = self.queryset + serializer = AccountSummarySerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") + + def retrieve(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = AccountSummarySerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") + + def create(self, request): + serializer = AccountRegisterSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + username = serializer.validated_data.pop('username') + password = serializer.validated_data.pop('password') + + user = User.objects.create_user( + username=username, + password=password + ) + account = Account.objects.create( + user=user, + **serializer.validated_data + ) + + return Response( + AccountSummarySerializer(account).data, + status=status.HTTP_201_CREATED, + content_type ="application/json", + ) + + def update(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = AccountSummarySerializer(instance, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + + return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") \ No newline at end of file diff --git a/project/settings.py b/project/settings.py index b5a80ff..788f6c0 100644 --- a/project/settings.py +++ b/project/settings.py @@ -79,7 +79,7 @@ WSGI_APPLICATION = "project.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -USE_POSTGRE = "DATABASE_IS_PSQL" in os.environ +USE_POSTGRE = os.environ.get('DATABASE_IS_PSQL', False) if USE_POSTGRE: DATABASES = { -- GitLab From b47267df956f4dbd4675638fccdb835b25d13b39 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 25 Feb 2020 00:34:01 +0700 Subject: [PATCH 5/7] [CHORES] Fix wrong content type on account viewset --- apps/accounts/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/accounts/views.py b/apps/accounts/views.py index a9490bd..d6254b1 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -13,12 +13,12 @@ class AccountViewSet(viewsets.ViewSet): def list(self, request): queryset = self.queryset serializer = AccountSummarySerializer(queryset, many=True) - return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") + return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) serializer = AccountSummarySerializer(instance) - return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") + return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request): serializer = AccountRegisterSerializer(data=request.data) @@ -39,7 +39,6 @@ class AccountViewSet(viewsets.ViewSet): return Response( AccountSummarySerializer(account).data, status=status.HTTP_201_CREATED, - content_type ="application/json", ) def update(self, request, pk=None): @@ -48,4 +47,4 @@ class AccountViewSet(viewsets.ViewSet): serializer.is_valid(raise_exception=True) serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK, content_type ="application/json") \ No newline at end of file + return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file -- GitLab From cb4350c99de6df35b4f4d0b5c09096895188939d Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 25 Feb 2020 12:59:00 +0700 Subject: [PATCH 6/7] [REFACTOR] Reformat code stylings --- apps/accounts/apps.py | 2 +- apps/accounts/migrations/0001_initial.py | 34 +++++--- apps/accounts/serializers.py | 45 +++++----- apps/accounts/tests/factories/accounts.py | 12 ++- .../tests/test_units/test_accounts.py | 86 ++++++++----------- apps/accounts/urls.py | 4 +- apps/accounts/views.py | 34 ++++---- manage.py | 4 +- project/asgi.py | 2 +- project/settings.py | 29 ++++--- project/urls.py | 4 +- project/wsgi.py | 2 +- 12 files changed, 128 insertions(+), 130 deletions(-) diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py index 9b3fc5a..fb0257e 100644 --- a/apps/accounts/apps.py +++ b/apps/accounts/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): - name = 'accounts' + name = "accounts" diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index afe3f72..d633945 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -16,17 +16,31 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Account', + name="Account", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128)), - ('email', models.EmailField(max_length=128)), - ('phone_number', models.CharField(max_length=64)), - ('area', models.CharField(max_length=128)), - ('is_admin', models.BooleanField(default=False)), - ('is_verified', models.BooleanField(default=False)), - ('is_active', models.BooleanField(default=False)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=128)), + ("email", models.EmailField(max_length=128)), + ("phone_number", models.CharField(max_length=64)), + ("area", models.CharField(max_length=128)), + ("is_admin", models.BooleanField(default=False)), + ("is_verified", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py index 6cd91c0..fbf73f7 100644 --- a/apps/accounts/serializers.py +++ b/apps/accounts/serializers.py @@ -1,26 +1,27 @@ from rest_framework import serializers from apps.accounts.models import Account + class AccountSummarySerializer(serializers.ModelSerializer): - username = serializers.CharField(source='user.username', required=False) + username = serializers.CharField(source="user.username", required=False) class Meta: model = Account fields = [ - 'id', - 'username', - 'name', - 'email', - 'phone_number', - 'area', - 'is_admin', - 'is_verified', - 'is_active', + "id", + "username", + "name", + "email", + "phone_number", + "area", + "is_admin", + "is_verified", + "is_active", ] read_only_fields = [ - 'id', - 'username', + "id", + "username", ] @@ -31,14 +32,14 @@ class AccountRegisterSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ - 'id', - 'username', - 'password', - 'name', - 'email', - 'phone_number', - 'area', - 'is_admin', - 'is_verified', - 'is_active', + "id", + "username", + "password", + "name", + "email", + "phone_number", + "area", + "is_admin", + "is_verified", + "is_active", ] diff --git a/apps/accounts/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py index 4441a3d..27ff6a7 100644 --- a/apps/accounts/tests/factories/accounts.py +++ b/apps/accounts/tests/factories/accounts.py @@ -12,19 +12,17 @@ class UserFactory(factory.DjangoModelFactory): class Meta: model = User - username = 'username' - password = 'justpass' + username = "username" + password = "justpass" class AccountFactory(factory.DjangoModelFactory): class Meta: model = Account - + class Params: - admin = factory.Trait( - is_admin=True, - ) - + admin = factory.Trait(is_admin=True,) + user = factory.SubFactory(UserFactory) name = faker.name() email = faker.email() diff --git a/apps/accounts/tests/test_units/test_accounts.py b/apps/accounts/tests/test_units/test_accounts.py index 83fd2f7..4afa9de 100644 --- a/apps/accounts/tests/test_units/test_accounts.py +++ b/apps/accounts/tests/test_units/test_accounts.py @@ -10,101 +10,89 @@ from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory class AccountViewTest(TestCase): def setUp(self): - self.user_1 = UserFactory(username='user_1') - self.user_2 = UserFactory(username='user_2') + self.user_1 = UserFactory(username="user_1") + self.user_2 = UserFactory(username="user_2") self.admin = AccountFactory(admin=True, user=self.user_1) self.officer = AccountFactory(admin=False, user=self.user_2) self.accounts = [self.admin, self.officer] self.faker = Faker() - + def test_list_all_accounts(self): - url = '/accounts/' + url = "/accounts/" response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_create_new_admin(self): - url = '/accounts/' - + url = "/accounts/" + _account_id = self.faker.email() admin_prev_count = Account.objects.filter(is_admin=True).count() data = { - 'name': self.faker.name(), - 'username': _account_id, - 'password': 'justpass', - 'email': _account_id, - 'phone_number': self.faker.phone_number(), - 'area': self.faker.city(), - 'is_admin': True, + "name": self.faker.name(), + "username": _account_id, + "password": "justpass", + "email": _account_id, + "phone_number": self.faker.phone_number(), + "area": self.faker.city(), + "is_admin": True, } response = self.client.post( - path=url, - data=data, - content_type='application/json', - format='json', + path=url, data=data, content_type="application/json", format="json", ) admin_current_count = Account.objects.filter(is_admin=True).count() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(admin_current_count, admin_prev_count + 1) - def test_create_new_officer(self): - url = '/accounts/' - + url = "/accounts/" + _account_id = self.faker.email() officer_prev_count = Account.objects.filter(is_admin=False).count() data = { - 'name': self.faker.name(), - 'username': _account_id, - 'password': 'justpass', - 'email': _account_id, - 'phone_number': self.faker.phone_number(), - 'area': self.faker.city(), - 'is_admin': False, + "name": self.faker.name(), + "username": _account_id, + "password": "justpass", + "email": _account_id, + "phone_number": self.faker.phone_number(), + "area": self.faker.city(), + "is_admin": False, } response = self.client.post( - path=url, - data=data, - content_type='application/json', - format='json', + path=url, data=data, content_type="application/json", format="json", ) officer_current_count = Account.objects.filter(is_admin=False).count() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(officer_current_count, officer_prev_count + 1) - def test_edit_account(self): - url = '/accounts/' + str(self.officer.id) + '/' - + url = "/accounts/" + str(self.officer.id) + "/" + data = { - 'id': str(self.officer.id), - 'name': self.faker.name(), - 'email': self.faker.email(), - 'phone_number': self.faker.phone_number(), - 'area': self.faker.city(), - 'is_admin': False, - 'is_verified': True, - 'is_active': True, + "id": str(self.officer.id), + "name": self.faker.name(), + "email": self.faker.email(), + "phone_number": self.faker.phone_number(), + "area": self.faker.city(), + "is_admin": False, + "is_verified": True, + "is_active": True, } response = self.client.put( - path=url, - data=data, - content_type='application/json', - format='json', + path=url, data=data, content_type="application/json", format="json", ) expected_returned_data = data - expected_returned_data['username'] = self.officer.user.username + expected_returned_data["username"] = self.officer.user.username self.assertJSONEqual( - str(response.content, encoding='utf8'), - expected_returned_data + str(response.content, encoding="utf8"), expected_returned_data ) diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index b73f577..789e0a0 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -21,10 +21,10 @@ from apps.accounts.views import AccountViewSet router = DefaultRouter() -router.register(r'', AccountViewSet, basename='account') +router.register(r"", AccountViewSet, basename="account") urlpatterns = [ - path('auth/', include('django.contrib.auth.urls')), + path("auth/", include("django.contrib.auth.urls")), ] urlpatterns += router.urls diff --git a/apps/accounts/views.py b/apps/accounts/views.py index d6254b1..c943a17 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -4,41 +4,37 @@ from rest_framework import status, viewsets from rest_framework.response import Response from apps.accounts.models import Account -from apps.accounts.serializers import AccountSummarySerializer, AccountRegisterSerializer +from apps.accounts.serializers import ( + AccountSummarySerializer, + AccountRegisterSerializer, +) class AccountViewSet(viewsets.ViewSet): - queryset = Account.objects.all().select_related('user') - + queryset = Account.objects.all().select_related("user") + def list(self, request): queryset = self.queryset serializer = AccountSummarySerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - + def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) serializer = AccountSummarySerializer(instance) return Response(serializer.data, status=status.HTTP_200_OK) - + def create(self, request): serializer = AccountRegisterSerializer(data=request.data) serializer.is_valid(raise_exception=True) - username = serializer.validated_data.pop('username') - password = serializer.validated_data.pop('password') - - user = User.objects.create_user( - username=username, - password=password - ) - account = Account.objects.create( - user=user, - **serializer.validated_data - ) + username = serializer.validated_data.pop("username") + password = serializer.validated_data.pop("password") + + user = User.objects.create_user(username=username, password=password) + account = Account.objects.create(user=user, **serializer.validated_data) return Response( - AccountSummarySerializer(account).data, - status=status.HTTP_201_CREATED, + AccountSummarySerializer(account).data, status=status.HTTP_201_CREATED, ) def update(self, request, pk=None): @@ -47,4 +43,4 @@ class AccountViewSet(viewsets.ViewSet): serializer.is_valid(raise_exception=True) serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/manage.py b/manage.py index 364337c..af30516 100755 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ import sys def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/project/asgi.py b/project/asgi.py index 76b40d7..c50ce26 100644 --- a/project/asgi.py +++ b/project/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") application = get_asgi_application() diff --git a/project/settings.py b/project/settings.py index 788f6c0..6b28cd5 100644 --- a/project/settings.py +++ b/project/settings.py @@ -14,6 +14,7 @@ import os # Get all local env variables from dotenv import load_dotenv + load_dotenv() # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -79,26 +80,26 @@ WSGI_APPLICATION = "project.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -USE_POSTGRE = os.environ.get('DATABASE_IS_PSQL', False) +USE_POSTGRE = os.environ.get("DATABASE_IS_PSQL", False) if USE_POSTGRE: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': os.environ.get('DATABASE_NAME'), - 'USER': os.environ.get('DATABASE_USER'), - 'PASSWORD': os.environ.get('DATABASE_PASSWORD'), - 'HOST': os.environ.get('DATABASE_HOST'), - 'PORT': os.environ.get('DATABASE_PORT'), + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": os.environ.get("DATABASE_NAME"), + "USER": os.environ.get("DATABASE_USER"), + "PASSWORD": os.environ.get("DATABASE_PASSWORD"), + "HOST": os.environ.get("DATABASE_HOST"), + "PORT": os.environ.get("DATABASE_PORT"), } } else: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + } } -} # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators @@ -130,6 +131,6 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") -STATIC_URL = '/static/' +STATIC_URL = "/static/" diff --git a/project/urls.py b/project/urls.py index 35f2a4d..d2bd5c3 100644 --- a/project/urls.py +++ b/project/urls.py @@ -17,6 +17,6 @@ from django.contrib import admin from django.urls import path, include urlpatterns = [ - path('admin/', admin.site.urls), - path('accounts/', include('apps.accounts.urls')), + path("admin/", admin.site.urls), + path("accounts/", include("apps.accounts.urls")), ] diff --git a/project/wsgi.py b/project/wsgi.py index 3bef15f..92ee525 100644 --- a/project/wsgi.py +++ b/project/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") application = get_wsgi_application() -- GitLab From 693f139ac5909e101f0c6484430900d1be4704f5 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 26 Feb 2020 10:07:37 +0700 Subject: [PATCH 7/7] [CHORES] Update test and coverage list --- .gitlab-ci.yml | 2 +- .../tests/test_units/test_accounts.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a3fd8e2..5bed570 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,7 +39,7 @@ test: script: - echo "Starting tests" - coverage erase - - coverage run --include="./*/*" --omit="./env/*","./manage.py" manage.py test apps + - coverage run --include="./*/*" --omit="./env/*","./project/*","./manage.py" manage.py test apps - coverage xml -i - coverage report -m artifacts: diff --git a/apps/accounts/tests/test_units/test_accounts.py b/apps/accounts/tests/test_units/test_accounts.py index 4afa9de..158a3b3 100644 --- a/apps/accounts/tests/test_units/test_accounts.py +++ b/apps/accounts/tests/test_units/test_accounts.py @@ -24,6 +24,26 @@ class AccountViewTest(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_retrieve_account(self): + url = "/accounts/" + str(self.officer.id) + "/" + + response = self.client.get(url) + + data = { + "id": str(self.officer.id), + "name": self.officer.name, + "username": self.officer.user.username, + "email": self.officer.email, + "phone_number": self.officer.phone_number, + "area": self.officer.area, + "is_admin": False, + "is_verified": False, + "is_active": False, + } + + self.assertJSONEqual(json.dumps(response.data), data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_create_new_admin(self): url = "/accounts/" -- GitLab