Skip to content

Commit

Permalink
Add House creation logic after login as a user
Browse files Browse the repository at this point in the history
Add Create House View
fixing House tests and model to new house model
fixing expenses tests and model to new house model
Fixing create mock data
Adding test for script
Adding test for factories
Change the House model to be with a one-to-one relationship with the Django user model
  • Loading branch information
ronyyosef committed May 16, 2022
1 parent 1b05fc5 commit 3522092
Show file tree
Hide file tree
Showing 24 changed files with 285 additions and 204 deletions.
8 changes: 4 additions & 4 deletions expenses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def create_expense(house_name, amount, date, category):

@staticmethod
def average_expenses_of_houses_by_categories(houses) -> QuerySet:
return (
Expenses.objects.filter(house_name__name__in=houses.values_list('name')).order_by().values(
'category').annotate(average=Avg("amount", output_field=IntegerField()))
)
filtered_expenses = Expenses.objects.filter(
house_name__user__in=houses.values_list('user')).order_by().values(
'category').annotate(average=Avg("amount", output_field=IntegerField()))
return filtered_expenses
1 change: 1 addition & 0 deletions factories/house.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class HouseFactory(DjangoModelFactory):
class Meta:
model = House

user = factory.LazyAttribute(lambda x: x.user)
name = factory.Faker('name')
public = factory.Faker('boolean')
country = factory.Iterator(Country.objects.all())
Expand Down
32 changes: 32 additions & 0 deletions factories/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from factories.expense import ExpenseFactory
from factories.house import HouseFactory
from factories.user import UserFactory


@pytest.fixture
def user_factory():
return UserFactory()


@pytest.fixture
def house_factory(user_factory):
return HouseFactory(user=user_factory)


@pytest.fixture
def expense_factory(house_factory):
return ExpenseFactory(house=house_factory, month=1)


@pytest.mark.django_db()
class TestFactories:
def test_user_factory(self):
UserFactory()

def test_house_factory(self, user_factory):
HouseFactory(user=user_factory)

def test_expense_factory(self, house_factory):
ExpenseFactory(house=house_factory, month=1)
11 changes: 11 additions & 0 deletions factories/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import factory
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password


class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()

username = factory.Sequence(lambda n: "username%d" % n)
password = make_password("password")
1 change: 1 addition & 0 deletions house/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
LOGIN_PAGE_ROUTE = 'login/login.html'
USER_LOGIN_PAGE_ROUTE = 'registration/login.html'
USER_SIGNUP_ROUTE = 'registration/signup.html'
HOUSE_CREATE_ROUTE = 'house_view/house_create.html'
14 changes: 11 additions & 3 deletions house/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms
from house.models import House, Job

from house.models import House, Job, Country, City


class HouseForm(forms.ModelForm):
Expand All @@ -21,5 +22,12 @@ def __init__(self, *args, **kwargs):
self.fields['children'].required = False


class HouseIDForm(forms.Form):
house_id = forms.UUIDField(label='Enter Your House ID')
class HouseCreationForm(forms.Form):
name = forms.CharField(label='House Name', required=True)
parent_profession_1 = forms.ChoiceField(choices=Job.choices, required=True)
parent_profession_2 = forms.ChoiceField(choices=Job.choices, required=True)
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True)
city = forms.ModelChoiceField(queryset=City.objects.all(), required=True)
income = forms.IntegerField(label='Monthly Income', required=True, min_value=0)
children = forms.IntegerField(label='Number of Children', required=True, min_value=0, max_value=20)
public = forms.BooleanField(label='public', required=True, initial=True)
7 changes: 4 additions & 3 deletions house/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Generated by Django 4.0.3 on 2022-04-06 21:09
# Generated by Django 4.0.3 on 2022-05-10 15:44

from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
Expand All @@ -31,7 +32,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='House',
fields=[
('house_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('name', models.CharField(max_length=50)),
('public', models.BooleanField(default=False)),
('parent_profession_1', models.CharField(choices=[('Teacher', 'Teacher'), ('Student', 'Student'), ('Programmer', 'Programmer'), ('Artist', 'Artist'), ('Manager', 'Manager'), ('Army', 'Army'), ('Police', 'Police'), ('Doctor', 'Doctor'), ('Vet', 'Vet'), ('Nurse', 'Nurse'), ('Technichian', 'Technichian'), ('Cleaner', 'Cleaner'), ('Other', 'Other'), ('Unemployed', 'Unemployed')], default='Other', max_length=50)),
Expand Down
14 changes: 7 additions & 7 deletions house/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import uuid
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from django.db import models


class Country(models.Model):
Expand Down Expand Up @@ -46,7 +46,7 @@ class Job(models.TextChoices):


class House(models.Model):
house_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
name = models.CharField(max_length=50)
public = models.BooleanField(default=False)
country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True)
Expand All @@ -58,10 +58,10 @@ class House(models.Model):
description = models.TextField(max_length=250, default='')

@staticmethod
def create_house(
name, public, country, city, parent_profession_1, parent_profession_2, income, children, description
):
def create_house(user, name, public, country, city, parent_profession_1, parent_profession_2, income, children,
description=''):
house = House(
user=user,
name=name,
public=public,
country=country,
Expand All @@ -70,7 +70,7 @@ def create_house(
parent_profession_2=parent_profession_2,
income=income,
children=children,
description=description,
description=description
)
house.save()
return house
Expand Down
5 changes: 2 additions & 3 deletions house/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
urlpatterns = [
path('', views.home_page, name='home'),
path('global/', views.global_page, name='global_page'),
path('login/', views.house_login, name='house_login'),
path('house/<str:house_id>/', views.house_view, name='house_view'),
path('house/add', views.add_house, name='add_house'),
path('house_create/', views.house_create, name='house_create'),
path('house/', views.house_view, name='house_view'),
path('ajax/load-cities/', views.load_cities, name='ajax_load_cities'),
]
72 changes: 37 additions & 35 deletions house/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from house.constants import MINE_MINE_PAGE_ROUTE
from django.shortcuts import render, get_object_or_404
from house.constants import HOME_PAGE_ROUTE, GLOBAL_PAGE_ROUTE, GLOBAL_PAGE_CITY_DROPDOWN_ROUTE, LOGIN_PAGE_ROUTE
from .forms import HouseForm, HouseIDForm
from .helpers import _filter_houses_by_form
from expenses.models import Expenses
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.core.exceptions import ValidationError
from django.shortcuts import render

from expenses.models import Expenses
from house.constants import HOME_PAGE_ROUTE, GLOBAL_PAGE_ROUTE, GLOBAL_PAGE_CITY_DROPDOWN_ROUTE
from house.constants import MINE_MINE_PAGE_ROUTE, HOUSE_CREATE_ROUTE
from .forms import HouseForm, HouseCreationForm
from .helpers import _filter_houses_by_form
from .models import House, City


Expand Down Expand Up @@ -33,43 +34,44 @@ def global_page(request):
return render(request, GLOBAL_PAGE_ROUTE, context)


def house_login(request):
errorMsg = ""

if request.method == 'POST':
try:
form = HouseIDForm(request.POST)
house_id = form.data["house_id"]

if House.objects.filter(house_id=house_id).count() == 1:
return HttpResponseRedirect(f'/../house/{house_id}')
else:
errorMsg = "There is no House with the provided ID"
form = HouseIDForm()
# In case an exception not a valid UUID is thrown
except ValidationError:
errorMsg = "There is no House with the provided ID : " + house_id
form = HouseIDForm()
else:
form = HouseIDForm()

return render(request, LOGIN_PAGE_ROUTE, {'form': form, 'msg': errorMsg})

@login_required
def house_view(request):
user = request.user
if hasattr(user, 'house') is False:
return HttpResponseRedirect('/../house_create')

def house_view(request, house_id):
house = get_object_or_404(House, pk=house_id)
house = user.house
expenses_list = Expenses.objects.filter(house_name=house)
context = {'house': house,
'house_expenses': expenses_list}
return render(request, MINE_MINE_PAGE_ROUTE, context)


def add_house(request):
raise NotImplementedError


def load_cities(request):
country_id = request.GET.get('country')
cities = City.objects.filter(country_id=country_id).order_by('name')
context = {'cities': cities}
return render(request, GLOBAL_PAGE_CITY_DROPDOWN_ROUTE, context)


@login_required
def house_create(request):
errorMsg = ""
if request.method == 'POST':
house_form = HouseCreationForm(request.POST)
if house_form.is_valid():
cleaned_data = house_form.cleaned_data
House.create_house(user=request.user,
name=cleaned_data['name'],
public=cleaned_data['public'],
parent_profession_1=cleaned_data['parent_profession_1'],
parent_profession_2=cleaned_data['parent_profession_2'],
country=cleaned_data['country'],
city=cleaned_data['city'],
children=cleaned_data['children'],
income=cleaned_data['income'])
return HttpResponseRedirect('/../house')
else:
house_form = HouseCreationForm()

return render(request, HOUSE_CREATE_ROUTE, {'form': house_form, 'msg': errorMsg})
18 changes: 14 additions & 4 deletions scripts/create_mock_data.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import random
from factories.expense import ExpenseFactory
from factories.house import HouseFactory
from factories.user import UserFactory


def run():
for _ in range(500):
house = HouseFactory()
def run(*args):
if args:
number_to_create = int(args[0])
else:
print("Please provide a number of records to create.\n"
"Example: manage.py runscript create_mock_data --script-args 10.")
return
print(f"Creating {number_to_create} users with mock data")
for _ in range(number_to_create):
user = UserFactory()
user.save()
house = HouseFactory(user=user)
house.save()
for _ in range(5):
ExpenseFactory(house=house, month=random.randint(1, 12)).save()
house.save()
16 changes: 16 additions & 0 deletions scripts/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest
from django.contrib.auth.models import User

from expenses.models import Expenses
from house.models import House
from scripts.create_mock_data import run


@pytest.mark.django_db()
class TestScripts:
def test_create_mock_data(self):
mock_num_for_test = 10
run(mock_num_for_test)
assert len(User.objects.all()) == mock_num_for_test
assert len(House.objects.all()) == mock_num_for_test
assert len(Expenses.objects.all()) == mock_num_for_test * 5
9 changes: 9 additions & 0 deletions static/css/house_create.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#house_create{
align-content: center;
margin-top: 5%;
margin-bottom: auto;
}

#submit-button{
width: 100%;
}
19 changes: 0 additions & 19 deletions static/css/login.css

This file was deleted.

2 changes: 1 addition & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<a class="nav-link" href="/global">Global</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login">Mine</a>
<a class="nav-link" href="/house">Mine</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/tips">Tips</a>
Expand Down
22 changes: 22 additions & 0 deletions templates/house_view/house_create.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% load static %}
{% load material_form %}
{% block content %}
<link rel="stylesheet" type="text/css" href="{% static 'css/house_create.css' %}"/>
<div id="house_create">
<div class="d-flex justify-content-center h-100">
<div class="card">
<div class="card-header">
<h3>Add your House information</h3>
</div>
<div class="card-body" style="color: rgb(0, 0, 0); background: rgba(252, 252, 252, 0.3); width: 500px;">
<form method="POST">
{% csrf_token %}
{% form form=form %}{% endform %}
<button class="btn btn-primary" id="submit-button" type="submit">Submit</button>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
24 changes: 0 additions & 24 deletions templates/login/login.html

This file was deleted.

Loading

0 comments on commit 3522092

Please sign in to comment.