Skip to content
This repository was archived by the owner on Dec 19, 2021. It is now read-only.

Commit 0d1d5f0

Browse files
authored
Remove init method on tenant model (#183)
* Remove init method on tenant model * Create tests to ensure staffIDs are validated
1 parent e358b30 commit 0d1d5f0

File tree

9 files changed

+128
-134
lines changed

9 files changed

+128
-134
lines changed

data/seedData.py

+30-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from models.contact_number import ContactNumberModel
1212
from models.lease import LeaseModel
1313
from utils.time import time_format
14-
from schemas import PropertySchema
14+
from schemas import PropertySchema, TenantSchema
1515

1616

1717
def seedData():
@@ -158,24 +158,35 @@ def seedData():
158158
)
159159

160160

161-
tenant_renty_mcrenter = TenantModel(firstName="Renty",
162-
lastName="McRenter",
163-
phone="800-RENT-ALOT",
164-
staffIDs=[user_1.id, user_2.id]
165-
)
166-
tenant_renty_mcrenter.save_to_db()
167-
tenant_soho_muless = TenantModel(firstName="Soho",
168-
lastName="Muless",
169-
phone="123-123-0000",
170-
staffIDs=[]
171-
)
172-
tenant_soho_muless.save_to_db()
173-
tenant_starvin_artist = TenantModel(firstName="Starvin",
174-
lastName="Artist",
175-
phone="123-123-1111",
176-
staffIDs=[]
177-
)
178-
tenant_starvin_artist.save_to_db()
161+
tenant_renty_mcrenter = TenantModel.create(
162+
schema=TenantSchema,
163+
payload={
164+
'firstName': "Renty",
165+
'lastName': "McRenter",
166+
'phone': "800-RENT-ALOT",
167+
'staffIDs': [user_janice_joinstaff.id, user_hector_chen.id]
168+
}
169+
)
170+
171+
tenant_soho_muless = TenantModel.create(
172+
schema=TenantSchema,
173+
payload={
174+
'firstName': "Soho",
175+
'lastName': "Muless",
176+
'phone': "123-123-0000",
177+
'staffIDs': []
178+
}
179+
)
180+
181+
tenant_starvin_artist = TenantModel.create(
182+
schema=TenantSchema,
183+
payload={
184+
'firstName': "Starvin",
185+
'lastName': "Artist",
186+
'phone': "123-123-1111",
187+
'staffIDs': []
188+
}
189+
)
179190

180191
ticket_roof_on_fire = TicketModel(
181192
issue="The roof, the roof, the roof is on fire.",

models/tenant.py

-15
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,6 @@ class TenantModel(BaseModel):
1818
leases = db.relationship('LeaseModel',
1919
backref='tenant', lazy=True, cascade="all, delete-orphan")
2020

21-
def __init__(self, firstName, lastName, phone, staffIDs):
22-
self.firstName = firstName
23-
self.lastName = lastName
24-
self.phone = phone
25-
self.staff = []
26-
if not (staffIDs == None):
27-
for id in staffIDs:
28-
user = UserModel.find_by_id(id)
29-
if user: self.staff.append(user)
30-
31-
3221
def json(self):
3322
return {
3423
'id': self.id,
@@ -40,7 +29,3 @@ def json(self):
4029
'created_at': Time.format_date(self.created_at),
4130
'updated_at': Time.format_date(self.updated_at)
4231
}
43-
44-
@classmethod
45-
def find_by_first_and_last(cls, first, last):
46-
return cls.query.filter_by(firstName=first, lastName=last).first()

resources/tenants.py

+4-48
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
import json
2-
from flask_restful import Resource, reqparse
1+
from flask_restful import Resource
32
from flask import request
43
from utils.authorizations import admin_required
5-
from db import db
64
from models.tenant import TenantModel
7-
from models.user import UserModel
85
from models.lease import LeaseModel
96
from schemas.lease import LeaseSchema
10-
from datetime import datetime
11-
from utils.time import Time
12-
from schemas.lease import LeaseSchema
13-
from datetime import datetime
7+
from schemas.tenant import TenantSchema
148

159
# | method | route | action |
1610
# | :----- | :------------------- | :------------------------ |
@@ -21,12 +15,6 @@
2115
# | DELETE | `v1/tenants/:id` | Deletes a single tenant |
2216

2317
class Tenants(Resource):
24-
parser = reqparse.RequestParser()
25-
parser.add_argument('firstName',type=str,required=True,help="This field cannot be blank")
26-
parser.add_argument('lastName',type=str,required=True,help="This field cannot be blank")
27-
parser.add_argument('phone',type=str,required=True,help="This field cannot be blank")
28-
parser.add_argument('staffIDs',action='append',required=False,help="This field can be provided at a later time")
29-
3018
@admin_required
3119
def get(self, tenant_id=None):
3220
# GET /tenants
@@ -42,12 +30,7 @@ def get(self, tenant_id=None):
4230

4331
@admin_required
4432
def post(self):
45-
data = Tenants.parser.parse_args()
46-
if TenantModel.find_by_first_and_last(data["firstName"], data["lastName"]):
47-
return { 'message': 'A tenant with this first and last name already exists'}, 401
48-
49-
tenantEntry = TenantModel(**data)
50-
TenantModel.save_to_db(tenantEntry)
33+
tenantEntry = TenantModel.create(schema=TenantSchema, payload=request.json)
5134

5235
returnData = tenantEntry.json()
5336

@@ -67,34 +50,7 @@ def post(self):
6750

6851
@admin_required
6952
def put(self, tenant_id):
70-
parser_for_put = Tenants.parser.copy()
71-
parser_for_put.replace_argument('firstName',required=False)
72-
parser_for_put.replace_argument('lastName',required=False)
73-
parser_for_put.replace_argument('phone',required=False)
74-
data = parser_for_put.parse_args()
75-
76-
tenantEntry = TenantModel.find_by_id(tenant_id)
77-
if not tenantEntry:
78-
return {'message': 'Tenant not found'}, 404
79-
80-
#variable statements allow for only updated fields to be transmitted
81-
if(data.firstName):
82-
tenantEntry.firstName = data.firstName
83-
if(data.lastName):
84-
tenantEntry.lastName = data.lastName
85-
if(data.phone):
86-
tenantEntry.phone = data.phone
87-
if(data.staffIDs and len(data.staffIDs)):
88-
tenantEntry.staff[:] = []
89-
for id in data.staffIDs:
90-
user = UserModel.find_by_id(id)
91-
if user: tenantEntry.staff.append(user)
92-
93-
94-
tenantEntry.save_to_db()
95-
96-
return tenantEntry.json()
97-
53+
return TenantModel.update(schema=TenantSchema, payload=request.json, id=tenant_id).json()
9854

9955
@admin_required
10056
def delete(self, tenant_id):

schemas/tenant.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from ma import ma
22
from models.tenant import TenantModel
3-
from marshmallow import fields, validate
3+
from models.user import UserModel
4+
from marshmallow import fields, validate, validates, post_load
45
from utils.time import time_format
6+
from schemas.staff_tenants import StaffTenantSchema
57

68

79
class TenantSchema(ma.SQLAlchemyAutoSchema):
@@ -11,6 +13,8 @@ class Meta:
1113
created_at = fields.DateTime(time_format)
1214
updated_at = fields.DateTime(time_format)
1315

16+
staffIDs = fields.List(fields.Integer(), required=False)
17+
1418
firstName = fields.Str(
1519
required=True,
1620
validate=validate.Length(min=1, max=100),
@@ -25,6 +29,17 @@ class Meta:
2529

2630
phone = fields.Str(
2731
required=True,
28-
validate=validate.Length(min=10, max=20),
32+
validate=validate.Length(min=10, max=30),
2933
error_messages={'required': 'Phone number is required.'},
3034
)
35+
36+
@validates("staffIDs")
37+
def validate_staff_ids(self, value):
38+
StaffTenantSchema().load({'staff': value}, partial=True)
39+
40+
@post_load
41+
def make_tenant_attributes(self, data, **kwargs):
42+
if 'staffIDs' in data:
43+
data['staff'] = [ UserModel.find(staff) for staff in data['staffIDs'] ]
44+
del(data['staffIDs'])
45+
return data

tests/factory_fixtures/lease.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ def _lease_attributes(unitNum, tenant, property):
1818
@pytest.fixture
1919
def create_lease(faker, lease_attributes, create_property, create_tenant):
2020
def _create_lease(unitNum=faker.building_number()):
21-
propertyID= create_property()
2221
tenant = create_tenant()
23-
lease = LeaseModel(**lease_attributes(unitNum, tenant, propertyID))
22+
lease = LeaseModel(**lease_attributes(unitNum, tenant, create_property()))
2423
lease.save_to_db()
2524
return lease
2625
yield _create_lease

tests/factory_fixtures/tenant.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import pytest
22
from models.tenant import TenantModel
3+
from schemas.tenant import TenantSchema
34

45
@pytest.fixture
5-
def create_tenant(faker):
6+
def tenant_attributes(faker, create_join_staff):
7+
def _tenant_attributes(staff=[create_join_staff().id]):
8+
return {
9+
'firstName': faker.first_name(),
10+
'lastName': faker.last_name(),
11+
'phone': faker.phone_number(),
12+
'staffIDs': staff
13+
}
14+
15+
yield _tenant_attributes
16+
17+
@pytest.fixture
18+
def create_tenant(tenant_attributes):
619
def _create_tenant():
7-
tenant = TenantModel(
8-
firstName=faker.first_name(),
9-
lastName=faker.last_name(),
10-
phone=faker.phone_number(),
11-
staffIDs=[]
12-
)
13-
tenant.save_to_db()
14-
return tenant
20+
return TenantModel.create(schema=TenantSchema, payload=tenant_attributes(staff=[]))
21+
1522
yield _create_tenant

tests/integration/test_tenants.py

+22-25
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ def test_tenants_GET_one(client, test_database, auth_headers):
2323
assert response.json == {'message': 'Tenant not found'}
2424

2525

26-
def test_tenants_POST(client, test_database, auth_headers, create_property):
26+
def test_tenants_POST(client, empty_test_db, auth_headers, valid_header, create_property, create_join_staff):
27+
staff_1 = create_join_staff()
28+
staff_2 = create_join_staff()
2729
newTenant = {
2830
"firstName": "Jake",
2931
"lastName": "The Dog",
3032
"phone": "111-111-1111",
31-
"staffIDs": [1, 2]
33+
"staffIDs": [staff_1.id, staff_2.id]
3234
}
3335

3436
newTenantWithLease = {
@@ -43,25 +45,20 @@ def test_tenants_POST(client, test_database, auth_headers, create_property):
4345
}
4446

4547
response = client.post(endpoint, json=newTenant,
46-
headers=auth_headers["admin"])
48+
headers=valid_header)
4749

4850
assert is_valid(response, 201) # CREATED
4951
assert response.json['firstName'] == 'Jake'
5052

5153
response = client.post(endpoint, json=newTenantWithLease,
52-
headers=auth_headers["admin"])
54+
headers=valid_header)
5355
assert is_valid(response, 201)
5456
assert response.json['unitNum'] == '413'
5557

5658

5759

5860
response = client.post(endpoint, json=newTenant,
59-
headers=auth_headers["admin"])
60-
# UNAUTHORIZED - A tenant with this first and last name already exists
61-
assert is_valid(response, 401)
62-
assert response.json == \
63-
{'message':
64-
'A tenant with this first and last name already exists'}
61+
headers=valid_header)
6562

6663
response = client.post(endpoint, json=newTenant,
6764
headers=auth_headers["pm"])
@@ -76,30 +73,30 @@ def test_tenants_POST(client, test_database, auth_headers, create_property):
7673
assert is_valid(response, 401)
7774
assert response.json == {'message': 'Missing authorization header'}
7875

79-
newTenant = {}
80-
response = client.post(endpoint, json=newTenant,
81-
headers=auth_headers["admin"])
82-
# BAD REQUEST - {'firstName': 'This field cannot be blank.'}
83-
assert is_valid(response, 400)
84-
assert response.json == {'message':
85-
{'firstName': 'This field cannot be blank'}}
86-
87-
def test_tenants_PUT(client, auth_headers):
88-
id = 1
76+
def test_tenants_PUT(client, empty_test_db, valid_header, create_tenant, create_join_staff):
77+
tenant = create_tenant()
78+
staff_1 = create_join_staff()
79+
staff_2 = create_join_staff()
8980
updatedTenant = {
9081
"firstName": "Jake",
9182
"lastName": "The Dog",
9283
"phone": "111-111-1111",
93-
"staffIDs": [1, 2]
84+
"staffIDs": [staff_1.id, staff_2.id]
9485
}
95-
response = client.put(f'{endpoint}/{id}',
96-
json=updatedTenant, headers=auth_headers["admin"])
86+
response = client.put(
87+
f'{endpoint}/{tenant.id}',
88+
json=updatedTenant,
89+
headers=valid_header
90+
)
9791
assert is_valid(response, 200) # OK
9892
assert response.json['firstName'] == 'Jake'
9993

10094
id = 100
101-
response = client.put(f'{endpoint}/{id}',
102-
json=updatedTenant, headers=auth_headers["admin"])
95+
response = client.put(
96+
f'{endpoint}/{id}',
97+
json=updatedTenant,
98+
headers=valid_header
99+
)
103100
assert is_valid(response, 404) # NOT FOUND
104101
assert response.json == {'message': 'Tenant not found'}
105102

tests/schemas/test_tenant_schema.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import pytest
12
from datetime import datetime
3+
from models.tenant import TenantModel
24
from schemas import TenantSchema
35
from utils.time import Time
46

@@ -48,13 +50,13 @@ def test_lastName_max_100(self):
4850

4951
assert 'lastName' in validation_errors
5052

51-
def test_phone_min_10(self):
53+
def test_min_length_phone_number_validation(self):
5254
validation_errors = TenantSchema().validate({'phone': '1234'})
5355

5456
assert 'phone' in validation_errors
5557

56-
def test_phone_max_20(self):
57-
long_phone_number = '8' * 21
58+
def test_max_length_phone_number_validation(self):
59+
long_phone_number = '8' * 41
5860
validation_errors = TenantSchema().validate({'phone': long_phone_number})
5961

6062
assert 'phone' in validation_errors
@@ -72,3 +74,22 @@ def test_updated_at_is_dump_only(self):
7274
)
7375

7476
assert 'updated_at' in validation_errors
77+
78+
def test_staffIDs_are_validated(self, empty_test_db, create_property_manager):
79+
pm = create_property_manager()
80+
validation_error = TenantSchema().validate({"staffIDs": [pm.id]})
81+
82+
assert "staffIDs" in validation_error
83+
84+
@pytest.mark.usefixtures('empty_test_db')
85+
class TestPostLoadDeserialization:
86+
def test_tenant_creation(self, tenant_attributes, create_property, create_join_staff):
87+
staff_1 = create_join_staff()
88+
staff_2 = create_join_staff()
89+
90+
tenant_attrs = tenant_attributes(staff=[staff_1.id, staff_2.id])
91+
92+
tenant = TenantModel.create(schema=TenantSchema, payload=tenant_attrs)
93+
94+
assert tenant
95+
assert tenant.staff == [staff_1, staff_2]

0 commit comments

Comments
 (0)