Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/adjust login with cpf instead email #8

Merged
merged 12 commits into from
May 8, 2024
10 changes: 5 additions & 5 deletions apps/core/admin/custom_user_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class CustomUserAdmin(BaseUserAdmin):
form = CustomUserChangeForm
add_form = CustomUserCreationForm
list_display = [
"email",
"cpf",
"first_name",
"last_name",
"is_staff",
Expand All @@ -20,7 +20,7 @@ class CustomUserAdmin(BaseUserAdmin):
"Dados de acesso",
{
"fields": [
"email",
"cpf",
"password",
]
},
Expand Down Expand Up @@ -51,22 +51,22 @@ class CustomUserAdmin(BaseUserAdmin):
{
"classes": ["wide"],
"fields": [
"email",
"cpf",
"password_1",
"password_2",
],
},
),
]
search_fields = [
"email",
"cpf",
"first_name",
"last_name",
]
ordering = [
"first_name",
"last_name",
"email",
"cpf",
]
filter_horizontal = []

Expand Down
49 changes: 43 additions & 6 deletions apps/core/forms/custom_user_forms.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
19 changes: 11 additions & 8 deletions apps/core/managers/custom_user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions apps/core/migrations/0001_initial.py
100755 β†’ 100644
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
),
),
(
Expand Down
24 changes: 22 additions & 2 deletions apps/core/models/custom_user.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
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):
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",
Expand All @@ -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
10 changes: 5 additions & 5 deletions apps/core/tests/admin/custom_user_admin_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def setUp(self) -> None:
def test_list_display(self):
# Given
expected_list_display = [
"cpf",
"first_name",
"last_name",
"email",
"is_staff",
]

Expand All @@ -33,7 +33,7 @@ def test_list_filter(self):

def test_fieldsets(self):
# Given
expected_access_fieldsets = ["email", "password"]
expected_access_fieldsets = ["cpf", "password"]
expected_personal_info_fieldsets = ["first_name", "last_name"]
expected_permissions_fieldsets = [
"is_staff",
Expand Down Expand Up @@ -63,7 +63,7 @@ def test_fieldsets(self):
def test_add_fieldsets(self):
# Given
expected_add_fieldsets = [
"email",
"cpf",
"password_1",
"password_2",
]
Expand All @@ -80,7 +80,7 @@ def test_add_fieldsets(self):
def test_search_fields(self):
# Given
expected_search_fields = [
"email",
"cpf",
"first_name",
"last_name",
]
Expand All @@ -93,7 +93,7 @@ def test_ordering(self):
expected_ordering = [
"first_name",
"last_name",
"email",
"cpf",
]

# When/Then
Expand Down
12 changes: 7 additions & 5 deletions apps/core/tests/api/auth_token_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand All @@ -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)
Expand All @@ -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)"
Expand All @@ -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"]
Expand Down
12 changes: 9 additions & 3 deletions apps/core/tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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="[email protected]", 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,
)

Expand Down
3 changes: 3 additions & 0 deletions apps/core/tests/factories/custom_user_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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
Expand All @@ -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,
Expand Down
Loading