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

Commit 2cf3bf0

Browse files
authored
Add resource for inviting property managers (#418)
1 parent 354c03d commit 2cf3bf0

File tree

10 files changed

+84
-27
lines changed

10 files changed

+84
-27
lines changed

config/routes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Users,
99
)
1010
from resources.user_invite import UserInvite
11+
from resources.user_invite import PropertyManagerInviteResource
1112
from resources.users.pending_users import UsersPending
1213
from resources.reset_password import ResetPassword
1314
from resources.property import Properties, Property, ArchiveProperties
@@ -25,12 +26,13 @@ class Routes:
2526
@staticmethod
2627
def routing(app):
2728
api = Api(app, prefix="/api/")
29+
api.add_resource(UserInvite, "user/invite")
30+
api.add_resource(PropertyManagerInviteResource, "user/invite/property_manager")
2831
api.add_resource(UserRegister, "register")
2932
api.add_resource(Property, "properties/<int:id>")
3033
api.add_resource(Properties, "properties")
3134
api.add_resource(ArchiveProperties, "properties/archive")
3235
api.add_resource(User, "user/<int:id>")
33-
api.add_resource(UserInvite, "user/invite")
3436
api.add_resource(Users, "user")
3537
api.add_resource(UsersPending, "users/pending")
3638
api.add_resource(ArchiveUser, "user/archive/<int:user_id>")

models/users/property_manager.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ class PropertyManager(UserModel):
1111
"PropertyModel",
1212
secondary=PropertyAssignment.tablename(),
1313
collection_class=NobiruList,
14-
viewonly=True,
1514
)
1615

1716
def has_pm_privs(self):

resources/user_invite.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import string
2+
import random
13
from flask_restful import Resource
24
from flask import request
5+
36
from utils.authorizations import admin_required
47
from models.user import UserModel
5-
from schemas import UserSchema
8+
from models.users.property_manager import PropertyManager
9+
from schemas import UserSchema, PropertyManagerSchema
610
from resources.email import Email
7-
import string
8-
import random
911

1012

1113
class UserInvite(Resource):
@@ -18,3 +20,13 @@ def post(self):
1820
)
1921
Email.send_user_invite_msg(user)
2022
return {"message": "User Invited"}, 201
23+
24+
25+
class PropertyManagerInviteResource(Resource):
26+
@admin_required
27+
def post(self):
28+
property_manager = PropertyManager.create(
29+
schema=PropertyManagerSchema, payload=request.json
30+
)
31+
Email.send_user_invite_msg(property_manager)
32+
return {"message": "Property Manager Invited"}, 201

schemas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .tenant import TenantSchema
22
from .property import PropertySchema
33
from .lease import LeaseSchema
4-
from .property_assignment import PropertyAssignSchema
4+
from .property_assignment import PropertyAssignmentSchema
55
from .user import *
66
from .staff_tenants import StaffTenantSchema
77
from .ticket import TicketSchema

schemas/property.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ma import ma
22
from models.property import PropertyModel
33
from models.users.property_manager import PropertyManager
4-
from schemas.property_assignment import PropertyAssignSchema
4+
from schemas.property_assignment import PropertyAssignmentSchema
55
from marshmallow import fields, validates, ValidationError, post_load
66
from utils.time import time_format
77
from tests.helpers.matchers import blank
@@ -20,7 +20,7 @@ def validates_property_manager_ids(self, value):
2020
payload = [
2121
{"manager_id": value[manager_id]} for manager_id in range(len(value))
2222
]
23-
PropertyAssignSchema().load(payload, partial=True, many=True)
23+
PropertyAssignmentSchema().load(payload, partial=True, many=True)
2424

2525
@validates("name")
2626
def validates_uniqueness_of_name(self, value):

schemas/property_assignment.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
from ma import ma
2+
from marshmallow import validates, ValidationError
3+
24
from models.property_assignment import PropertyAssignment
3-
from models.user import UserModel
45
from models.users.property_manager import PropertyManager
56
from models.property import PropertyModel
6-
from marshmallow import fields, validates, ValidationError
77

88

9-
class PropertyAssignSchema(ma.SQLAlchemyAutoSchema):
9+
class PropertyAssignmentSchema(ma.SQLAlchemyAutoSchema):
1010
class Meta:
1111
model = PropertyAssignment
1212
include_fk = True
13-
relations = True
14-
15-
propertyID = fields.Nested("PropertySchema")
16-
managerID = fields.Nested("UserSchema", required=True)
17-
18-
@validates("manager_id")
19-
def validate_is_valid_user(self, value):
20-
if not UserModel.query.get(value):
21-
raise ValidationError("not a valid user id")
2213

2314
@validates("property_id")
2415
def validate_is_valid_property(self, value):

schemas/user.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from ma import ma
2-
from marshmallow import fields, validates, ValidationError
2+
from marshmallow import fields, validates, ValidationError, post_load
33

44
from models.user import UserModel, RoleEnum
5+
from models.property import PropertyModel
6+
from schemas.property_assignment import PropertyAssignmentSchema
57

68

79
class UserSchema(ma.SQLAlchemyAutoSchema):
@@ -37,3 +39,19 @@ def user_cannot_register_a_role(self, _):
3739
@validates("type")
3840
def user_cannot_register_a_type(self, _):
3941
raise ValidationError("Type is not allowed")
42+
43+
44+
class PropertyManagerSchema(UserSchema):
45+
property_ids = fields.List(fields.Integer(), required=False)
46+
47+
@validates("property_ids")
48+
def validate_property_ids(self, ids):
49+
for id in ids:
50+
PropertyAssignmentSchema().load({"property_id": id}, partial=True)
51+
52+
@post_load
53+
def make_property_attributes(self, data, **kwargs):
54+
if "property_ids" in data:
55+
data["properties"] = [PropertyModel.find(id) for id in data["property_ids"]]
56+
del data["property_ids"]
57+
return data

tests/integration/test_user_invite.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import pytest
2-
from models.user import RoleEnum
32
from unittest.mock import patch
3+
4+
from models.user import RoleEnum
45
from resources.email import Email
6+
from models.users.property_manager import PropertyManager
7+
from schemas.user import PropertyManagerSchema
58

69

710
@pytest.mark.usefixtures("client_class")
@@ -20,3 +23,30 @@ def test_invite_user(self, send_user_invite_msg, valid_header, user_attributes):
2023
send_user_invite_msg.assert_called()
2124
assert response.status_code == 201
2225
assert response.json == {"message": "User Invited"}
26+
27+
28+
@pytest.mark.usefixtures("client_class")
29+
class TestPropertyManagerInvite:
30+
def setup(self):
31+
self.endpoint = "/api/user/invite/property_manager"
32+
33+
@patch.object(Email, "send_user_invite_msg")
34+
def test_invite_property_manager(
35+
self, send_user_invite_msg, user_attributes, valid_header
36+
):
37+
payload = user_attributes()
38+
39+
with patch.object(PropertyManager, "create") as mock_create_pm:
40+
response = self.client.post(
41+
self.endpoint,
42+
headers=valid_header,
43+
json=payload,
44+
)
45+
46+
send_user_invite_msg.assert_called()
47+
mock_create_pm.assert_called_once_with(
48+
schema=PropertyManagerSchema, payload=payload
49+
)
50+
51+
assert response.status_code == 201
52+
assert response.json == {"message": "Property Manager Invited"}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
from schemas.property_assignment import PropertyAssignSchema
1+
from schemas.property_assignment import PropertyAssignmentSchema
22

33

4-
class TestPropertyAssignSchemaValidation:
4+
class TestPropertyAssignmentSchemaValidation:
55
def test_foreign_key_constraints(self):
6-
validation = PropertyAssignSchema().validate(
6+
validation = PropertyAssignmentSchema().validate(
77
{"manager_id": 23, "property_id": 22}
88
)
99
assert "manager_id", "property_id" in validation
1010

1111
def test_manager_id_is_property_manager_pk(self, create_join_staff):
1212
invalid_data = {"manager_id": create_join_staff().id, "property_id": 3}
13-
validation_error = PropertyAssignSchema().validate(invalid_data)
13+
validation_error = PropertyAssignmentSchema().validate(invalid_data)
1414
assert "manager_id", "property_id" in validation_error

tests/schemas/test_user_schema.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from schemas.user import UserSchema, UserRegisterSchema
1+
from schemas.user import UserSchema, UserRegisterSchema, PropertyManagerSchema
22
from models.user import RoleEnum, UserType
33

44

@@ -86,3 +86,8 @@ def test_password_is_required(self):
8686
validation_errors = UserRegisterSchema().validate({})
8787

8888
assert "password" in validation_errors
89+
90+
91+
class TestInheritedPropertyManagerSchemaValidations(UserSchemaValidations):
92+
def setup(self):
93+
self.schema = PropertyManagerSchema

0 commit comments

Comments
 (0)