From 68c2a96bd495d90a825211418dd76781bcf77c11 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 13:31:09 -0300 Subject: [PATCH 01/10] feature/adjust user model to use cpf as login field --- apps/core/admin/custom_user_admin.py | 10 ++-- apps/core/forms/custom_user_forms.py | 49 ++++++++++++++++--- apps/core/managers/custom_user_manager.py | 19 ++++--- apps/core/migrations/0001_initial.py | 13 ++++- apps/core/models/custom_user.py | 24 ++++++++- .../tests/admin/custom_user_admin_tests.py | 9 ++-- apps/core/tests/api/auth_token_tests.py | 12 +++-- apps/core/tests/base_test.py | 12 +++-- .../tests/factories/custom_user_factory.py | 3 ++ .../tests/forms/custom_user_forms_tests.py | 11 +++-- .../managers/custom_user_manager_tests.py | 13 ++--- apps/core/tests/models/custom_user_tests.py | 5 +- 12 files changed, 133 insertions(+), 47 deletions(-) mode change 100755 => 100644 apps/core/migrations/0001_initial.py diff --git a/apps/core/admin/custom_user_admin.py b/apps/core/admin/custom_user_admin.py index f0cbb7a..a52c88c 100755 --- a/apps/core/admin/custom_user_admin.py +++ b/apps/core/admin/custom_user_admin.py @@ -9,9 +9,10 @@ class CustomUserAdmin(BaseUserAdmin): form = CustomUserChangeForm add_form = CustomUserCreationForm list_display = [ - "email", + "cpf", "first_name", "last_name", + "email", "is_staff", ] list_filter = ["is_staff"] @@ -20,6 +21,7 @@ class CustomUserAdmin(BaseUserAdmin): "Dados de acesso", { "fields": [ + "cpf", "email", "password", ] @@ -51,7 +53,7 @@ class CustomUserAdmin(BaseUserAdmin): { "classes": ["wide"], "fields": [ - "email", + "cpf", "password_1", "password_2", ], @@ -59,14 +61,14 @@ class CustomUserAdmin(BaseUserAdmin): ), ] search_fields = [ - "email", + "cpf", "first_name", "last_name", ] ordering = [ "first_name", "last_name", - "email", + "cpf", ] filter_horizontal = [] diff --git a/apps/core/forms/custom_user_forms.py b/apps/core/forms/custom_user_forms.py index c82dcf8..6851c96 100755 --- a/apps/core/forms/custom_user_forms.py +++ b/apps/core/forms/custom_user_forms.py @@ -1,8 +1,8 @@ from django import forms from django.contrib.auth.forms import ReadOnlyPasswordHashField -from django.core.exceptions import ValidationError -from apps.core.models import CustomUser +from apps.core.models.custom_user import CustomUser +from apps.core.utils.cpf_validator import CpfValidator class CustomUserCreationForm(forms.ModelForm): @@ -11,31 +11,68 @@ class CustomUserCreationForm(forms.ModelForm): widget=forms.PasswordInput, ) password_2 = forms.CharField( - label="Confirmação de senha", widget=forms.PasswordInput + label="Confirmação de senha", + widget=forms.PasswordInput, ) class Meta: model = CustomUser - fields = ["email"] + fields = [ + "cpf", + "first_name", + "last_name", + "email", + "is_staff", + ] def clean_password_2(self): password_1 = self.cleaned_data.get("password_1") password_2 = self.cleaned_data.get("password_2") + if password_1 and password_2 and password_1 != password_2: - raise ValidationError("Passwords don't match") + raise forms.ValidationError("Passwords don't match") + return password_2 + def clean_cpf(self): + cpf = self.cleaned_data.get("cpf") + return validate_cpf(cpf) + def save(self, commit=True): + # Save the provided password in hashed format user = super().save(commit=False) user.set_password(self.cleaned_data["password_1"]) + if commit: user.save() + return user class CustomUserChangeForm(forms.ModelForm): + # Form for updating users, with hashed password password = ReadOnlyPasswordHashField() class Meta: model = CustomUser - fields = ["email", "password", "is_active", "is_staff"] + fields = [ + "cpf", + "first_name", + "last_name", + "email", + "is_staff", + ] + + def clean_cpf(self): + cpf = self.cleaned_data.get("cpf") + return validate_cpf(cpf) + + +def validate_cpf(cpf: str) -> str: + validator = CpfValidator() + cpf_is_valid = validator.validate_cpf(cpf) + + if not cpf_is_valid: + raise forms.ValidationError("CPF digitado é inválido") + + return cpf diff --git a/apps/core/managers/custom_user_manager.py b/apps/core/managers/custom_user_manager.py index 38e3faa..81abc39 100755 --- a/apps/core/managers/custom_user_manager.py +++ b/apps/core/managers/custom_user_manager.py @@ -2,24 +2,27 @@ class CustomUserManager(BaseUserManager): - def create_user(self, email, password=None): + def create_user(self, cpf, password=None): """ - Creates and saves a User with the given email and password. + Creates and saves a User with the given cpf and password. """ - user = self.model( - email=self.normalize_email(email), - ) + if not cpf: + raise ValueError("Users must have an cpf number") + + if not password: + raise ValueError("Users must have a password") + user = self.model(cpf=cpf) user.set_password(password) user.save(using=self._db) return user - def create_superuser(self, email, password=None): + def create_superuser(self, cpf, password=None): """ - Creates and saves a superuser with the given email and password. + Creates and saves a superuser with the given cpf and password. """ user = self.create_user( - email, + cpf=cpf, password=password, ) user.is_staff = True diff --git a/apps/core/migrations/0001_initial.py b/apps/core/migrations/0001_initial.py old mode 100755 new mode 100644 index e18eb63..ad8b88d --- a/apps/core/migrations/0001_initial.py +++ b/apps/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.11 on 2024-03-12 20:46 +# Generated by Django 4.2.11 on 2024-05-08 15:04 import django.contrib.auth.models from django.db import migrations, models @@ -94,10 +94,19 @@ class Migration(migrations.Migration): verbose_name="date joined", ), ), + ( + "cpf", + models.CharField( + max_length=11, unique=True, verbose_name="CPF" + ), + ), ( "email", models.EmailField( - max_length=255, unique=True, verbose_name="E-mail" + blank=True, + max_length=255, + null=True, + verbose_name="E-mail", ), ), ( diff --git a/apps/core/models/custom_user.py b/apps/core/models/custom_user.py index c94693c..28a4d86 100755 --- a/apps/core/models/custom_user.py +++ b/apps/core/models/custom_user.py @@ -1,7 +1,12 @@ +from uuid import uuid4 + from django.contrib.auth.models import AbstractUser +from django.core.exceptions import ValidationError from django.db import models from apps.core.managers import CustomUserManager +from apps.core.utils.cpf_validator import CpfValidator +from apps.core.utils.regex_utils import get_only_numbers class CustomUser(AbstractUser): @@ -9,10 +14,16 @@ class Meta: verbose_name = "Usuário" verbose_name_plural = "Usuários" + cpf = models.CharField( + max_length=11, + verbose_name="CPF", + unique=True, + ) email = models.EmailField( verbose_name="E-mail", max_length=255, - unique=True, + null=True, + blank=True, ) username = models.CharField( verbose_name="Nome de usuário", @@ -23,8 +34,17 @@ class Meta: objects = CustomUserManager() - USERNAME_FIELD = "email" # This is the field that will be used to login + USERNAME_FIELD = "cpf" # This is the field that will be used to login REQUIRED_FIELDS = [] + def save(self, *args, **kwargs): + self.cpf = get_only_numbers(self.cpf) + cpf_is_valid = CpfValidator().validate_cpf(self.cpf) + + if not cpf_is_valid: + raise ValidationError({"cpf": "CPF inválido."}) + + super().save(*args, **kwargs) + def __str__(self): return self.first_name diff --git a/apps/core/tests/admin/custom_user_admin_tests.py b/apps/core/tests/admin/custom_user_admin_tests.py index 582f560..5df1bbb 100755 --- a/apps/core/tests/admin/custom_user_admin_tests.py +++ b/apps/core/tests/admin/custom_user_admin_tests.py @@ -13,6 +13,7 @@ def setUp(self) -> None: def test_list_display(self): # Given expected_list_display = [ + "cpf", "first_name", "last_name", "email", @@ -33,7 +34,7 @@ def test_list_filter(self): def test_fieldsets(self): # Given - expected_access_fieldsets = ["email", "password"] + expected_access_fieldsets = ["cpf", "email", "password"] expected_personal_info_fieldsets = ["first_name", "last_name"] expected_permissions_fieldsets = [ "is_staff", @@ -63,7 +64,7 @@ def test_fieldsets(self): def test_add_fieldsets(self): # Given expected_add_fieldsets = [ - "email", + "cpf", "password_1", "password_2", ] @@ -80,7 +81,7 @@ def test_add_fieldsets(self): def test_search_fields(self): # Given expected_search_fields = [ - "email", + "cpf", "first_name", "last_name", ] @@ -93,7 +94,7 @@ def test_ordering(self): expected_ordering = [ "first_name", "last_name", - "email", + "cpf", ] # When/Then diff --git a/apps/core/tests/api/auth_token_tests.py b/apps/core/tests/api/auth_token_tests.py index b31bf78..ed4e4c8 100755 --- a/apps/core/tests/api/auth_token_tests.py +++ b/apps/core/tests/api/auth_token_tests.py @@ -4,17 +4,19 @@ from rest_framework import status from rest_framework.test import APITestCase +from apps.core.utils.regex_utils import get_only_numbers + fake = Faker("pt_BR") class AuthTokenTest(APITestCase): def setUp(self) -> None: super().setUp() - self.email = fake.email() + self.cpf = get_only_numbers(fake.cpf()) self.password = fake.password() self.user_model = get_user_model() self.user = self.user_model.objects.create_user( - email=self.email, + cpf=self.cpf, password=self.password, ) @@ -29,7 +31,7 @@ def test_auth_token_refresh_url_resolves(self): def test_auth_token_returns_400_when_missing_required_fields(self): # Given url = reverse("token_obtain_pair") - expected_required_fields = ["email", "password"] + expected_required_fields = ["cpf", "password"] # When response = self.client.post(url) @@ -44,7 +46,7 @@ def test_auth_token_returns_401_when_invalid_credentials(self): # Given url = reverse("token_obtain_pair") payload = { - "email": self.email, + "cpf": self.cpf, "password": fake.password(), } expected_message = "Usuário e/ou senha incorreto(s)" @@ -61,7 +63,7 @@ def test_auth_token_returns_200_when_valid_credentials(self): # Given url = reverse("token_obtain_pair") payload = { - "email": self.email, + "cpf": self.cpf, "password": self.password, } expected_keys = ["refresh", "access"] diff --git a/apps/core/tests/base_test.py b/apps/core/tests/base_test.py index e826d26..19fa118 100755 --- a/apps/core/tests/base_test.py +++ b/apps/core/tests/base_test.py @@ -3,6 +3,12 @@ from faker import Faker from rest_framework.test import APIClient, override_settings +from apps.core.utils.regex_utils import get_only_numbers + +fake = Faker("pt_BR") + +FAKE_CPF = get_only_numbers(fake.cpf()) + @override_settings( DEFAULT_FILE_STORAGE="django.core.files.storage.memory.InMemoryStorage" @@ -12,12 +18,12 @@ def setUp(self): self.user = self.create_test_user() self.auth_client = self.create_authenticated_client() self.unauth_client = APIClient() - self.fake = Faker("pt_BR") + self.fake = fake - def create_test_user(self, email="test@email.com", password="testpassword"): + def create_test_user(self, cpf=FAKE_CPF, password="testpassword"): user_model = get_user_model() return user_model.objects.create_user( - email=email, + cpf=cpf, password=password, ) diff --git a/apps/core/tests/factories/custom_user_factory.py b/apps/core/tests/factories/custom_user_factory.py index 070da68..8cae50e 100755 --- a/apps/core/tests/factories/custom_user_factory.py +++ b/apps/core/tests/factories/custom_user_factory.py @@ -2,6 +2,7 @@ from faker import Faker from apps.core.models.custom_user import CustomUser +from apps.core.utils.regex_utils import get_only_numbers fake = Faker("pt_BR") @@ -13,6 +14,7 @@ class Meta: first_name = fake.first_name() last_name = fake.last_name() email = fake.email() + cpf = get_only_numbers(fake.cpf()) is_staff = False is_active = True is_superuser = False @@ -24,6 +26,7 @@ def custom_user_data(cls) -> dict: "first_name": cls.first_name, "last_name": cls.last_name, "email": cls.email, + "cpf": cls.cpf, "is_staff": cls.is_staff, "is_active": cls.is_active, "is_superuser": cls.is_superuser, diff --git a/apps/core/tests/forms/custom_user_forms_tests.py b/apps/core/tests/forms/custom_user_forms_tests.py index dbe4a32..b6dfc85 100755 --- a/apps/core/tests/forms/custom_user_forms_tests.py +++ b/apps/core/tests/forms/custom_user_forms_tests.py @@ -3,6 +3,7 @@ from faker import Faker from apps.core.forms.custom_user_forms import CustomUserCreationForm +from apps.core.utils.regex_utils import get_only_numbers fake = Faker("pt_BR") @@ -14,7 +15,7 @@ def setUp(self) -> None: self.form_data = { "first_name": fake.first_name(), "last_name": fake.last_name(), - "email": fake.email(), + "cpf": get_only_numbers(fake.cpf()), "is_staff": True, "password_1": self.fake_password, "password_2": self.fake_password, @@ -29,18 +30,18 @@ def test_custom_user_creation_form_successfully(self): # Then self.assertTrue(form.is_valid()) - def test_custom_user_creation_form_with_invalid_email(self): + def test_custom_user_creation_form_with_invalid_cpf(self): # Given form_data = self.form_data - form_data["email"] = "Invalid E-mail" - expected_error = ["Insira um endereço de email válido."] + form_data["cpf"] = "00000000000" + expected_error = ["CPF digitado é inválido"] # When form = CustomUserCreationForm(data=form_data) # Then self.assertFalse(form.is_valid()) - self.assertEqual(form.errors["email"], expected_error) + self.assertEqual(form.errors["cpf"], expected_error) def test_custom_user_creation_form_with_passwords_not_matching(self): # Given diff --git a/apps/core/tests/managers/custom_user_manager_tests.py b/apps/core/tests/managers/custom_user_manager_tests.py index 44aaa95..6bd7b07 100755 --- a/apps/core/tests/managers/custom_user_manager_tests.py +++ b/apps/core/tests/managers/custom_user_manager_tests.py @@ -2,6 +2,7 @@ from faker import Faker from apps.core.models import CustomUser +from apps.core.utils.regex_utils import get_only_numbers fake = Faker("pt_BR") @@ -9,26 +10,26 @@ class CustomUserManagerTest(TestCase): def test_create_user_creates_user(self): # Given - email = fake.email() + cpf = get_only_numbers(fake.cpf()) password = fake.password() # When - user = CustomUser.objects.create_user(email, password) + user = CustomUser.objects.create_user(cpf, password) # Then - self.assertEqual(user.email, email) + self.assertEqual(user.cpf, cpf) self.assertTrue(user.check_password(password)) def test_create_superuser_creates_superuser(self): # Given - email = fake.email() + cpf = get_only_numbers(fake.cpf()) password = fake.password() # When - user = CustomUser.objects.create_superuser(email, password) + user = CustomUser.objects.create_superuser(cpf, password) # Then - self.assertEqual(user.email, email) + self.assertEqual(user.cpf, cpf) self.assertTrue(user.check_password(password)) self.assertTrue(user.is_staff) self.assertTrue(user.is_superuser) diff --git a/apps/core/tests/models/custom_user_tests.py b/apps/core/tests/models/custom_user_tests.py index 5570fb0..6798a8a 100755 --- a/apps/core/tests/models/custom_user_tests.py +++ b/apps/core/tests/models/custom_user_tests.py @@ -15,6 +15,7 @@ def test_create_custom_user_model_instance(self): "first_name", "last_name", "email", + "cpf", "is_staff", "is_active", "is_superuser", @@ -35,11 +36,11 @@ def test_create_custom_user_model_instance(self): db_user_attr = getattr(db_user, attr) self.assertEqual(user_attr, db_user_attr) - def test_create_custom_user_model_instance_if_email_already_exists(self): + def test_create_custom_user_model_instance_if_cpf_already_exists(self): # Given user = CustomUserFactory() new_user_data = CustomUserFactory.custom_user_data() - new_user_data["email"] = user.email + new_user_data["cpf"] = user.cpf expected_error = "duplicate key value violates unique constraint" # When From 527d9eac44563ab4ea1ad89f47b30880a8a8844d Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 13:45:04 -0300 Subject: [PATCH 02/10] refactor/adjust person model --- apps/ride_manager/admin/person_admin.py | 5 +++++ apps/ride_manager/migrations/0001_initial.py | 10 ++-------- apps/ride_manager/models/person.py | 14 -------------- .../ride_manager/tests/factories/person_factory.py | 2 -- apps/ride_manager/tests/person_model_tests.py | 12 ------------ 5 files changed, 7 insertions(+), 36 deletions(-) diff --git a/apps/ride_manager/admin/person_admin.py b/apps/ride_manager/admin/person_admin.py index fee1a34..2762f56 100644 --- a/apps/ride_manager/admin/person_admin.py +++ b/apps/ride_manager/admin/person_admin.py @@ -5,6 +5,11 @@ @admin.register(Person) class PersonAdmin(admin.ModelAdmin): + def cpf(self, obj): + return obj.user.cpf + + cpf.short_description = "CPF" + list_display = [ "name", "phone", diff --git a/apps/ride_manager/migrations/0001_initial.py b/apps/ride_manager/migrations/0001_initial.py index 71f556d..b305107 100644 --- a/apps/ride_manager/migrations/0001_initial.py +++ b/apps/ride_manager/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.11 on 2024-05-08 13:50 +# Generated by Django 4.2.11 on 2024-05-08 16:36 import apps.ride_manager.models.person from django.conf import settings @@ -12,8 +12,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("address_manager", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -116,12 +116,6 @@ class Migration(migrations.Migration): default=False, verbose_name="CNH verificada" ), ), - ( - "cpf", - models.CharField( - max_length=11, unique=True, verbose_name="CPF" - ), - ), ( "document_picture", models.ImageField( diff --git a/apps/ride_manager/models/person.py b/apps/ride_manager/models/person.py index dace5da..4c2abe7 100644 --- a/apps/ride_manager/models/person.py +++ b/apps/ride_manager/models/person.py @@ -80,11 +80,6 @@ class Meta: verbose_name="CNH verificada", default=False, ) - cpf = models.CharField( - max_length=11, - verbose_name="CPF", - unique=True, - ) document_picture = models.ImageField( upload_to=upload_path, verbose_name="Imagem do documento", @@ -100,14 +95,5 @@ class Meta: default=False, ) - def save(self, *args, **kwargs): - self.cpf = get_only_numbers(self.cpf) - cpf_is_valid = CpfValidator().validate_cpf(self.cpf) - - if not cpf_is_valid: - raise ValidationError({"cpf": "CPF inválido."}) - - return super().save(*args, **kwargs) - def __str__(self): return self.name diff --git a/apps/ride_manager/tests/factories/person_factory.py b/apps/ride_manager/tests/factories/person_factory.py index 384aaf4..8cfe8a8 100644 --- a/apps/ride_manager/tests/factories/person_factory.py +++ b/apps/ride_manager/tests/factories/person_factory.py @@ -19,7 +19,6 @@ class PersonFactory(factory.django.DjangoModelFactory): cnh_picture = fake.file_name() document_picture = fake.file_name() cnh_number = fake.numerify("###########") - cpf = fake.cpf() city = factory.SubFactory(CityFactory) zip_code = fake.numerify("#####-###") @@ -35,7 +34,6 @@ def person_data() -> dict: "cnh_picture": fake.file_name(), "document_picture": fake.file_name(), "cnh_number": fake.numerify("###########"), - "cpf": fake.cpf(), "zip_code": fake.numerify("#####-###"), } diff --git a/apps/ride_manager/tests/person_model_tests.py b/apps/ride_manager/tests/person_model_tests.py index acad0ad..aa40c3a 100644 --- a/apps/ride_manager/tests/person_model_tests.py +++ b/apps/ride_manager/tests/person_model_tests.py @@ -20,7 +20,6 @@ def test_create_person_model_instance(self): "avatar", "cnh_number", "cnh_picture", - "cpf", "document_picture", "city", "zip_code", @@ -94,17 +93,6 @@ def test_person_create_raise_exception_when_missing_required_fields(self): with self.assertRaises(Exception): Person.objects.create(**person_data) - def test_person_create_raise_exception_when_invalid_cpf(self): - # Given - person_data = PersonFactory.person_data() - person_data["user"] = self.user - person_data["city"] = CityFactory() - person_data["cpf"] = "12345678901" - - # When/Then - with self.assertRaises(ValidationError): - Person.objects.create(**person_data) - def test_person_create_raise_exception_when_invalid_zip_code_format(self): # Given person_data = PersonFactory.person_data() From 31adabbc5ea9766449be449e983c1321c915a461 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:06:09 -0300 Subject: [PATCH 03/10] insert pre-commit action --- .github/workflows/main.yml | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..45aa834 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,46 @@ +name: Test, format, and check coverage + +on: + pull_request: + branches: [main] + +jobs: + test-format-and-check-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Run tests and calculate coverage + run: | + coverage run --source='.' manage.py test + COVERAGE_PERCENTAGE=$(coverage report --fail-under=90 | grep TOTAL | awk '{print $4}') + echo "Coverage: $COVERAGE_PERCENTAGE" + if [ $(echo "$COVERAGE_PERCENTAGE > 90" | bc -l) -eq 1 ]; then + echo "Coverage is above 90%, continuing with formatting." + black --check . + isort --check-only . + NEEDS_FORMATTING=$? + if [ $NEEDS_FORMATTING -eq 0 ]; then + echo "No formatting needed, continuing with commit." + else + echo "Formatting needed, formatting code." + black . + isort . + git add . + git commit -m "Format code with Black and isort" + fi + else + echo "Coverage is below 90%, blocking commit." + exit 1 + fi \ No newline at end of file From 3629cd3a6af2e8ea6223f58fb689c20e7390d356 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:22:15 -0300 Subject: [PATCH 04/10] insert test database --- config/settings/database.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/config/settings/database.py b/config/settings/database.py index bbaf59b..d21ad2d 100755 --- a/config/settings/database.py +++ b/config/settings/database.py @@ -1,12 +1,23 @@ +import os + from decouple import config -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "NAME": config("DB_NAME"), - "USER": config("DB_USER"), - "PASSWORD": config("DB_PASSWORD"), - "HOST": config("DB_HOST"), - "PORT": config("DB_PORT"), +if "GITHUB_ACTIONS" in os.environ: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } + } + +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": config("DB_NAME"), + "USER": config("DB_USER"), + "PASSWORD": config("DB_PASSWORD"), + "HOST": config("DB_HOST"), + "PORT": config("DB_PORT"), + } } -} From f7608c6bd255cec17c4632d3368666911321a3fc Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:25:23 -0300 Subject: [PATCH 05/10] insert default test envs --- config/settings/security.py | 4 ++-- config/settings/simple_jwt.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/settings/security.py b/config/settings/security.py index 9f05a67..ca6ac91 100755 --- a/config/settings/security.py +++ b/config/settings/security.py @@ -1,7 +1,7 @@ from decouple import config -SECRET_KEY = config("SECRET_KEY") -DEBUG = config("DEBUG") +SECRET_KEY = config("SECRET_KEY", default="-") +DEBUG = config("DEBUG", default=False, cast=bool) ALLOWED_HOSTS = ["*"] AUTH_PASSWORD_VALIDATORS = [ { diff --git a/config/settings/simple_jwt.py b/config/settings/simple_jwt.py index f2d3df0..2879eba 100755 --- a/config/settings/simple_jwt.py +++ b/config/settings/simple_jwt.py @@ -6,6 +6,6 @@ "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), "REFRESH_TOKEN_LIFETIME": timedelta(days=1), "BLACKLIST_AFTER_ROTATION": False, - "SIGNING_KEY": config("JWT_SECRET_KEY"), + "SIGNING_KEY": config("JWT_SECRET_KEY", default="secret"), "AUTH_HEADER_TYPES": ("Bearer",), } From e915920512cbe3e3a47500add926d7a7439f6f56 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:38:51 -0300 Subject: [PATCH 06/10] insert .coveragerc --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..7e68f35 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +omit = + */migrations/* + */tests/* From f29cd604774471dcc0324151df60d6ca886417a6 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:43:43 -0300 Subject: [PATCH 07/10] adjust coverage settings --- .coveragerc | 4 ---- .github/workflows/main.yml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 7e68f35..0000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -omit = - */migrations/* - */tests/* diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 45aa834..9bf962f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: - name: Run tests and calculate coverage run: | - coverage run --source='.' manage.py test + coverage run --source='.' --omit='*/migrations/*,*/tests/*' manage.py test COVERAGE_PERCENTAGE=$(coverage report --fail-under=90 | grep TOTAL | awk '{print $4}') echo "Coverage: $COVERAGE_PERCENTAGE" if [ $(echo "$COVERAGE_PERCENTAGE > 90" | bc -l) -eq 1 ]; then From 1143e511f9255845433ce3a022f4e7da3a2fe52d Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 14:59:08 -0300 Subject: [PATCH 08/10] adjust actions --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9bf962f..199fe3a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,9 +21,12 @@ jobs: run: | pip install -r requirements.txt - - name: Run tests and calculate coverage + - name: Run tests + run: | + pytest --cov-report term-missing --cov-report html + + - name: Check coverage run: | - coverage run --source='.' --omit='*/migrations/*,*/tests/*' manage.py test COVERAGE_PERCENTAGE=$(coverage report --fail-under=90 | grep TOTAL | awk '{print $4}') echo "Coverage: $COVERAGE_PERCENTAGE" if [ $(echo "$COVERAGE_PERCENTAGE > 90" | bc -l) -eq 1 ]; then From e51a8a195214b0ec074fbe36ba14d56c1829d881 Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 15:09:15 -0300 Subject: [PATCH 09/10] remove workflow --- .github/workflows/main.yml | 49 -------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 199fe3a..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Test, format, and check coverage - -on: - pull_request: - branches: [main] - -jobs: - test-format-and-check-coverage: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.11 - - - name: Install dependencies - run: | - pip install -r requirements.txt - - - name: Run tests - run: | - pytest --cov-report term-missing --cov-report html - - - name: Check coverage - run: | - COVERAGE_PERCENTAGE=$(coverage report --fail-under=90 | grep TOTAL | awk '{print $4}') - echo "Coverage: $COVERAGE_PERCENTAGE" - if [ $(echo "$COVERAGE_PERCENTAGE > 90" | bc -l) -eq 1 ]; then - echo "Coverage is above 90%, continuing with formatting." - black --check . - isort --check-only . - NEEDS_FORMATTING=$? - if [ $NEEDS_FORMATTING -eq 0 ]; then - echo "No formatting needed, continuing with commit." - else - echo "Formatting needed, formatting code." - black . - isort . - git add . - git commit -m "Format code with Black and isort" - fi - else - echo "Coverage is below 90%, blocking commit." - exit 1 - fi \ No newline at end of file From 4d73cb8d3793e93315824e64a7f37e4d89d05cfa Mon Sep 17 00:00:00 2001 From: Edmundo Abreu e Lima <98054216+engedmundo@users.noreply.github.com> Date: Wed, 8 May 2024 15:17:51 -0300 Subject: [PATCH 10/10] code review adjusts --- apps/core/admin/custom_user_admin.py | 2 -- .../tests/admin/custom_user_admin_tests.py | 3 +-- apps/ride_manager/admin/vehicle_admin.py | 7 ++---- apps/ride_manager/models/vehicle.py | 1 + .../tests/admin/vehicle_admin_tests.py | 4 ++-- .../tests/factories/vehicle_factory.py | 5 +++-- .../tests/models/vehicle_tests.py | 22 ++++++++++++++++--- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/core/admin/custom_user_admin.py b/apps/core/admin/custom_user_admin.py index a52c88c..90347be 100755 --- a/apps/core/admin/custom_user_admin.py +++ b/apps/core/admin/custom_user_admin.py @@ -12,7 +12,6 @@ class CustomUserAdmin(BaseUserAdmin): "cpf", "first_name", "last_name", - "email", "is_staff", ] list_filter = ["is_staff"] @@ -22,7 +21,6 @@ class CustomUserAdmin(BaseUserAdmin): { "fields": [ "cpf", - "email", "password", ] }, diff --git a/apps/core/tests/admin/custom_user_admin_tests.py b/apps/core/tests/admin/custom_user_admin_tests.py index 5df1bbb..c6c3534 100755 --- a/apps/core/tests/admin/custom_user_admin_tests.py +++ b/apps/core/tests/admin/custom_user_admin_tests.py @@ -16,7 +16,6 @@ def test_list_display(self): "cpf", "first_name", "last_name", - "email", "is_staff", ] @@ -34,7 +33,7 @@ def test_list_filter(self): def test_fieldsets(self): # Given - expected_access_fieldsets = ["cpf", "email", "password"] + expected_access_fieldsets = ["cpf", "password"] expected_personal_info_fieldsets = ["first_name", "last_name"] expected_permissions_fieldsets = [ "is_staff", diff --git a/apps/ride_manager/admin/vehicle_admin.py b/apps/ride_manager/admin/vehicle_admin.py index abd9a96..c5d9993 100644 --- a/apps/ride_manager/admin/vehicle_admin.py +++ b/apps/ride_manager/admin/vehicle_admin.py @@ -19,10 +19,7 @@ class VehicleAdmin(admin.ModelAdmin): "is_verified", "is_active", ] - ordering = [ - "model", - "plate" - ] + ordering = ["model", "plate"] readonly_fields = [ "uuid", "person", @@ -33,4 +30,4 @@ class VehicleAdmin(admin.ModelAdmin): "vehicle_picture", "created_at", "updated_at", - ] \ No newline at end of file + ] diff --git a/apps/ride_manager/models/vehicle.py b/apps/ride_manager/models/vehicle.py index 6068fd6..d593ac8 100644 --- a/apps/ride_manager/models/vehicle.py +++ b/apps/ride_manager/models/vehicle.py @@ -1,5 +1,6 @@ import os from uuid import uuid4 + from django.db import models from apps.core.models import BaseModel diff --git a/apps/ride_manager/tests/admin/vehicle_admin_tests.py b/apps/ride_manager/tests/admin/vehicle_admin_tests.py index 961abcb..451872f 100755 --- a/apps/ride_manager/tests/admin/vehicle_admin_tests.py +++ b/apps/ride_manager/tests/admin/vehicle_admin_tests.py @@ -1,10 +1,10 @@ -from django.contrib.admin import AdminSite import pytest +from django.contrib.admin import AdminSite +from apps.core.tests.base_test import BaseTest from apps.ride_manager.admin import VehicleAdmin from apps.ride_manager.models import Vehicle from apps.ride_manager.tests.factories import VehicleFactory -from apps.core.tests.base_test import BaseTest class VehicleAdminTest(BaseTest): diff --git a/apps/ride_manager/tests/factories/vehicle_factory.py b/apps/ride_manager/tests/factories/vehicle_factory.py index db6e255..00f1697 100755 --- a/apps/ride_manager/tests/factories/vehicle_factory.py +++ b/apps/ride_manager/tests/factories/vehicle_factory.py @@ -6,12 +6,13 @@ fake = Faker("pt_BR") + class VehicleFactory(factory.django.DjangoModelFactory): model = fake.name() color = fake.color_name() plate = fake.license_plate() - plate_picture = factory.django.ImageField(filename='plate_picture.jpg') - vehicle_picture = factory.django.ImageField(filename='vehicle_picture.jpg') + plate_picture = factory.django.ImageField(filename="plate_picture.jpg") + vehicle_picture = factory.django.ImageField(filename="vehicle_picture.jpg") is_verified = fake.boolean() person = factory.SubFactory(PersonFactory) diff --git a/apps/ride_manager/tests/models/vehicle_tests.py b/apps/ride_manager/tests/models/vehicle_tests.py index a1dc25d..0a6c239 100755 --- a/apps/ride_manager/tests/models/vehicle_tests.py +++ b/apps/ride_manager/tests/models/vehicle_tests.py @@ -1,14 +1,22 @@ import pytest +from apps.core.tests.base_test import BaseTest from apps.ride_manager.models import Vehicle from apps.ride_manager.tests.factories import VehicleFactory -from apps.core.tests.base_test import BaseTest class VehicleModelTests(BaseTest): def test_create_vehicle_model_instance(self): # Given - expected_attrs = ["model", "person", "color", "plate", "plate_picture", "vehicle_picture", "is_verified"] + expected_attrs = [ + "model", + "person", + "color", + "plate", + "plate_picture", + "vehicle_picture", + "is_verified", + ] # When vehicle = VehicleFactory() @@ -39,7 +47,15 @@ def test_vehicle_model_meta_verbose_names(self): def test_vehicle_create_raise_exception_when_missing_required_fields(self): # Given - required_fields = ["model", "person", "color", "plate", "plate_picture", "vehicle_picture", "is_verified"] + required_fields = [ + "model", + "person", + "color", + "plate", + "plate_picture", + "vehicle_picture", + "is_verified", + ] for field_name in required_fields: # When