Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[run]
source = myrealestate
omit =
*/migrations/*
*/tests/*
*/admin.py
*/apps.py
*/urls.py
*/__init__.py
*/asgi.py
*/wsgi.py
manage.py
*/settings.py


[report]
exclude_lines =
pragma: no cover
def __str__
def __repr__
raise NotImplementedError
if settings.DEBUG
pass
raise ImportError
if __name__ == .__main__.:

[html]
directory = coverage_html
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=127.0.0.1
EMAIL_PORT=1025 # Mailpit default SMTP port
EMAIL_USE_TLS=False
[email protected]
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
# MyRealEstate
A property management application

## Setup and Installation

### Prerequisites
- Python 3.8+
- Node.js and npm
- Docker (for MinIO)

### Initial Setup

1. Create and activate virtual environment:
```
python
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
Comment on lines +14 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Remove this superfluous line as it serves no purpose in the instructions

Suggested change
```
python
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate


1. Install dependencies:
```
pip install -r requirements.txt
```

1. Configure environment variables:
```
Comment on lines +25 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider referencing the .env.example file in the environment setup section

Since there's a .env.example file in the repository, it would be helpful to instruct users to copy it to .env as part of the setup process

Suggested change
1. Configure environment variables:
```
1. Configure environment variables:
```bash
# Copy the example environment file
cp .env.example .env
# Edit .env file to customize any settings if needed

# No variables needed for now
```

1. Run database migrations:
```
python manage.py migrate
```
### Tailwind CSS Setup

1. Install Tailwind dependencies:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The command for installing Tailwind dependencies is missing

Please add the specific command needed to install the Tailwind dependencies


1. Run the development server:
```
python manage.py runserver
```

File renamed without changes.
2 changes: 2 additions & 0 deletions myrealestate/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from myrealestate.common.forms import BaseModelForm, BaseForm
from myrealestate.companies.models import Company
from .models import UserTypeEnums


class CustomUserCreationForm(BaseModelForm, UserCreationForm):
email = forms.EmailField(
label=_("Email"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.1.3 on 2024-12-07 14:47

import uuid
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('accounts', '0002_usercompanyaccess_user_companies'),
]

operations = [
migrations.AddField(
model_name='user',
name='email_verification_token',
field=models.UUIDField(default=uuid.uuid4, null=True),
),
migrations.AddField(
model_name='user',
name='email_verified',
field=models.BooleanField(default=False),
),
]
21 changes: 21 additions & 0 deletions myrealestate/accounts/migrations/0004_populate_uuid_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.3 on 2024-12-07 14:52

import uuid
from django.db import migrations


def gen_uuid(apps, schema_editor):
User = apps.get_model("accounts", "User")
for row in User.objects.all():
row.email_verification_token = uuid.uuid4()
row.save(update_fields=["email_verification_token"])

class Migration(migrations.Migration):

dependencies = [
('accounts', '0003_user_email_verification_token_user_email_verified'),
]

operations = [
migrations.RunPython(gen_uuid, migrations.RunPython.noop),
]
19 changes: 19 additions & 0 deletions myrealestate/accounts/migrations/0005_remove_uuid_null.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.1.3 on 2024-12-07 14:52

import uuid
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('accounts', '0004_populate_uuid_fields'),
]

operations = [
migrations.AlterField(
model_name='user',
name='email_verification_token',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
9 changes: 9 additions & 0 deletions myrealestate/accounts/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import uuid
from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.utils.translation import gettext_lazy as _
from myrealestate.companies.models import Company
from myrealestate.common.models import BaseModel
from django.core.exceptions import ValidationError


class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
Expand All @@ -28,6 +31,12 @@ class User(AbstractUser):
related_name='users',
verbose_name=_('Companies'),
)
email_verified = models.BooleanField(default=False)
email_verification_token = models.UUIDField(unique=True, default=uuid.uuid4)

def generate_verification_token(self):
self.email_verification_token = uuid.uuid4()
self.save()

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
Expand Down
14 changes: 14 additions & 0 deletions myrealestate/accounts/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import factory
from django.contrib.auth import get_user_model
from ..models import UserCompanyAccess, UserTypeEnums

class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
django_get_or_create = ('email',)

email = factory.Sequence(lambda n: f'user{n}@example.com')
username = factory.Sequence(lambda n: f'username{n}')
password = factory.PostGenerationMethodCall('set_password', 'testpass123')
is_active = True

124 changes: 124 additions & 0 deletions myrealestate/accounts/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from django.test import TestCase
from django.urls import reverse
from ..forms import CustomUserCreationForm, CustomAuthenticationForm
from ..models import User, UserTypeEnums
from .factories import UserFactory
from myrealestate.companies.tests.factories import CompanyFactory
from myrealestate.companies.models import Company

class TestCustomUserCreationForm(TestCase):
def setUp(self):
self.valid_data = {
'email': '[email protected]',
'username': 'testuser',
'password1': 'StrongPass123!',
'password2': 'StrongPass123!',
'company_name': 'Test Company'
}

def test_form_valid_data(self):
"""Test form with valid data"""
form = CustomUserCreationForm(data=self.valid_data)
self.assertTrue(form.is_valid())

def test_form_creates_user_and_company(self):
"""Test that form creates both user and company with correct relationship"""
form = CustomUserCreationForm(data=self.valid_data)
self.assertTrue(form.is_valid())
user = form.save()

# Check user was created
self.assertIsInstance(user, User)
self.assertEqual(user.email, self.valid_data['email'])
self.assertEqual(user.username, self.valid_data['username'])

# Check company was created and linked
company = Company.objects.get(name=self.valid_data['company_name'])
self.assertTrue(user.companies.filter(id=company.id).exists())

# Check user is company owner
user_company = user.usercompanyaccess_set.get(company=company)
self.assertEqual(user_company.access_level, UserTypeEnums.COMPANY_OWNER)

def test_duplicate_email(self):
"""Test form validation with duplicate email"""
# Create a user first
UserFactory(email=self.valid_data['email'])

form = CustomUserCreationForm(data=self.valid_data)
self.assertFalse(form.is_valid())
self.assertIn('email', form.errors)
self.assertEqual(form.errors['email'][0], 'This email is already registered.')

def test_duplicate_username(self):
"""Test form validation with duplicate username"""
UserFactory(username=self.valid_data['username'])

form = CustomUserCreationForm(data=self.valid_data)
self.assertFalse(form.is_valid())
self.assertIn('username', form.errors)
self.assertEqual(form.errors['username'][0], 'This username is already taken.')

def test_password_mismatch(self):
"""Test form validation with mismatched passwords"""
data = self.valid_data.copy()
data['password2'] = 'DifferentPass123!'

form = CustomUserCreationForm(data=data)
self.assertFalse(form.is_valid())
self.assertIn('password2', form.errors)


class TestCustomAuthenticationForm(TestCase):
def setUp(self):
self.user = UserFactory(
email='[email protected]',
username='testuser'
)
self.user.set_password('TestPass123!')
self.user.save()

def test_login_with_email(self):
"""Test authentication using email"""
form = CustomAuthenticationForm(data={
'username': '[email protected]',
'password': 'TestPass123!'
})
self.assertTrue(form.is_valid())

def test_login_with_username(self):
"""Test authentication using username"""
form = CustomAuthenticationForm(data={
'username': 'testuser',
'password': 'TestPass123!'
})
self.assertTrue(form.is_valid())

def test_login_with_invalid_credentials(self):
"""Test authentication with wrong password"""
form = CustomAuthenticationForm(data={
'username': '[email protected]',
'password': 'WrongPass123!'
})
self.assertFalse(form.is_valid())
self.assertIn('__all__', form.errors)
self.assertEqual(
form.errors['__all__'][0],
'Please enter a correct email/username and password.'
)

def test_login_with_inactive_user(self):
"""Test authentication with inactive user"""
self.user.is_active = False
self.user.save()

form = CustomAuthenticationForm(data={
'username': '[email protected]',
'password': 'TestPass123!'
})
self.assertFalse(form.is_valid())
self.assertIn('__all__', form.errors)
self.assertEqual(
form.errors['__all__'][0],
'This account is inactive.'
)
44 changes: 44 additions & 0 deletions myrealestate/accounts/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.test import TestCase
from django.core.exceptions import ValidationError
from ..models import User, UserTypeEnums
from .factories import UserFactory

class TestUserModel(TestCase):
def setUp(self):
"""Set up data for all test methods"""
self.user = UserFactory()

def test_create_user(self):
"""Test creating a regular user"""
self.assertIsNotNone(self.user.pk)
self.assertIsNotNone(self.user.email)
self.assertIsNotNone(self.user.username)
self.assertTrue(self.user.check_password('testpass123'))
self.assertFalse(self.user.is_staff)
self.assertFalse(self.user.is_superuser)

def test_create_superuser(self):
"""Test creating a superuser"""
superuser = User.objects.create_superuser(
email='[email protected]',
password='adminpass123'
)
self.assertTrue(superuser.is_staff)
self.assertTrue(superuser.is_superuser)
self.assertEqual(superuser.email, '[email protected]')

def test_create_user_without_email_raises_error(self):
"""Test that creating a user without email raises error"""
with self.assertRaisesMessage(ValueError, 'Email is required'):
User.objects.create_user(email='', password='testpass123')

def test_user_str_method(self):
"""Test the string representation of User model"""
user = UserFactory(email='[email protected]')
self.assertEqual(str(user), '[email protected]')

def test_email_is_normalized(self):
"""Test email normalization"""
email = '[email protected]'
user = User.objects.create_user(email=email, password='testpass123')
self.assertEqual(user.email, '[email protected]')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Add tests for email verification token methods

The User model has new email verification features but there are no tests for generate_verification_token() method or the email_verified field behavior.

Loading
Loading