Skip to content

Commit

Permalink
Donor Onsite Contacts (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahanneda authored May 17, 2024
1 parent 2f4e25c commit c44fa4d
Show file tree
Hide file tree
Showing 35 changed files with 1,062 additions and 844 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ cd frontend
make test
```

### VSCode Python Test runner and debugger

Use VSCode's python test runner, which makes life MUCH Better.

- 1. Make sure you have the docker extension, and the `Dev Containers` extension installed.
- 2. Run `docker compose up backend --build` to start up backend container.
- 3. Use the docker extension, right click on the backend container, and click `Attach Visual Studio Code`.
- 4. Inside the container, make sure the Python Extension and the Python Debugger extension is installed.
- 5. Click the tests tab. Use the buttons to run or debug tests.
- 6. If you get a "Test not found" or similar error, the problem could be the wrong python binary is being used. In the bottom right, click on the python version, and try switching to different python binaries.

## Version Control Guide

### Branching
Expand Down
11 changes: 11 additions & 0 deletions backend/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"terminal.integrated.env.linux": {
"PYTHONPATH": "${workspaceFolder}"
},
"terminal.integrated.env.osx": {
"PYTHONPATH": "${workspaceFolder}"
},
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true

}
Empty file added backend/__init__.py
Empty file.
1 change: 1 addition & 0 deletions backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Config(object):
# put any configurations here that are common across all environments
# list of available configs: https://flask.palletsprojects.com/en/1.1.x/config/
MONGODB_URL = os.getenv("MG_DATABASE_URL")
MONGODB_DB_NAME = os.getenv("MG_DB_NAME")


class DevelopmentConfig(Config):
Expand Down
24 changes: 14 additions & 10 deletions backend/app/graphql/meal_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ class CreateMealRequestResponse(graphene.ObjectType):
drop_off_datetime = graphene.DateTime(required=True)
status = graphene.Field(graphene.Enum.from_enum(MealStatus), required=True)
meal_info = graphene.Field(MealInfoResponse, required=True)
onsite_staff = graphene.List(OnsiteContact)
onsite_contacts = graphene.List(OnsiteContact)


class DonationInfo(graphene.ObjectType):
donor = graphene.Field(User)
commitment_date = graphene.DateTime()
meal_description = graphene.String()
additional_info = graphene.String()
donor_onsite_contacts = graphene.List(OnsiteContact)


class MealRequestResponse(graphene.ObjectType):
Expand All @@ -54,7 +55,7 @@ class MealRequestResponse(graphene.ObjectType):
drop_off_datetime = graphene.DateTime()
drop_off_location = graphene.String()
meal_info = graphene.Field(MealInfoResponse)
onsite_staff = graphene.List(OnsiteContact)
onsite_contacts = graphene.List(OnsiteContact)
date_created = graphene.DateTime()
date_updated = graphene.DateTime()
delivery_instructions = graphene.String()
Expand All @@ -72,7 +73,7 @@ class Arguments:
drop_off_time = graphene.Time(required=True)
drop_off_location = graphene.String(required=True)
delivery_instructions = graphene.String(default_value=None)
onsite_staff = graphene.List(graphene.String, default_value=[])
onsite_contacts = graphene.List(graphene.String, default_value=[])

# return values
meal_requests = graphene.List(CreateMealRequestResponse)
Expand All @@ -86,7 +87,7 @@ def mutate(
drop_off_time,
drop_off_location,
delivery_instructions,
onsite_staff,
onsite_contacts,
):
result = services["meal_request_service"].create_meal_request(
requestor_id=requestor_id,
Expand All @@ -95,7 +96,7 @@ def mutate(
drop_off_time=drop_off_time,
drop_off_location=drop_off_location,
delivery_instructions=delivery_instructions,
onsite_staff=onsite_staff,
onsite_contacts=onsite_contacts,
)

return CreateMealRequests(meal_requests=result)
Expand All @@ -109,7 +110,7 @@ class Arguments:
meal_info = MealTypeInput()
drop_off_location = graphene.String()
delivery_instructions = graphene.String()
onsite_staff = graphene.List(graphene.String)
onsite_contacts = graphene.List(graphene.String)

# return values
meal_request = graphene.Field(MealRequestResponse)
Expand All @@ -123,15 +124,15 @@ def mutate(
meal_info=None,
drop_off_location=None,
delivery_instructions=None,
onsite_staff=None,
onsite_contacts=None,
):
result = services["meal_request_service"].update_meal_request(
requestor_id=requestor_id,
meal_info=meal_info,
drop_off_datetime=drop_off_datetime,
drop_off_location=drop_off_location,
delivery_instructions=delivery_instructions,
onsite_staff=onsite_staff,
onsite_contacts=onsite_contacts,
meal_request_id=meal_request_id,
)

Expand All @@ -144,6 +145,7 @@ class Arguments:
meal_request_ids = graphene.List(graphene.ID, required=True)
meal_description = graphene.String(required=True)
additional_info = graphene.String(default_value=None)
donor_onsite_contacts = graphene.List(graphene.ID, required=True)

meal_requests = graphene.List(MealRequestResponse)

Expand All @@ -154,12 +156,14 @@ def mutate(
meal_request_ids,
meal_description,
additional_info=None,
donor_onsite_contacts=[],
):
result = services["meal_request_service"].commit_to_meal_request(
donor_id=requestor,
meal_request_ids=meal_request_ids,
meal_description=meal_description,
additional_info=additional_info,
donor_onsite_contacts=donor_onsite_contacts,
)

return CommitToMealRequest(meal_requests=result)
Expand Down Expand Up @@ -316,7 +320,7 @@ def resolve_getMealRequestsByRequestorId(
drop_off_datetime=meal_request_dto.drop_off_datetime,
drop_off_location=meal_request_dto.drop_off_location,
meal_info=meal_request_dto.meal_info,
onsite_staff=meal_request_dto.onsite_staff,
onsite_contacts=meal_request_dto.onsite_contacts,
date_created=meal_request_dto.date_created,
date_updated=meal_request_dto.date_updated,
delivery_instructions=meal_request_dto.delivery_instructions,
Expand Down Expand Up @@ -370,7 +374,7 @@ def resolve_getMealRequestsByDonorId(
drop_off_datetime=meal_request_dto.drop_off_datetime,
drop_off_location=meal_request_dto.drop_off_location,
meal_info=meal_request_dto.meal_info,
onsite_staff=meal_request_dto.onsite_staff,
onsite_contacts=meal_request_dto.onsite_contacts,
date_created=meal_request_dto.date_created,
date_updated=meal_request_dto.date_updated,
delivery_instructions=meal_request_dto.delivery_instructions,
Expand Down
10 changes: 8 additions & 2 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@ def init_app(app):
if "USE_MONGOMOCK_CLIENT" in app.config
else pymongo.MongoClient
)
if "MONGODB_URL" in app.config:
connect(host=app.config["MONGODB_URL"], mongo_client_class=mongo_client)
if "MONGODB_URL" in app.config and "MONGODB_DB_NAME" in app.config:
connect(
host=app.config["MONGODB_URL"],
mongo_client_class=mongo_client,
db=app.config["MONGODB_DB_NAME"],
)
else:
raise Exception("MG_DATABASE_URL and MG_DB_NAME must be set in the env file.")
57 changes: 52 additions & 5 deletions backend/app/models/meal_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from enum import Enum

from app.models.onsite_contact import OnsiteContact
from app.resources.meal_request_dto import MealRequestDTO

from .user import User

Expand Down Expand Up @@ -30,6 +31,10 @@ class DonationInfo(mg.EmbeddedDocument):
commitment_date = mg.DateTimeField(required=True)
meal_description = mg.StringField(required=True)
additional_info = mg.StringField(default=None)
# https://docs.mongoengine.org/apireference.html#mongoengine.fields.ReferenceField
donor_onsite_contacts = mg.ListField(
mg.ReferenceField(OnsiteContact, required=True)
) # 4 = PULL


class MealRequest(mg.Document):
Expand All @@ -44,7 +49,7 @@ class MealRequest(mg.Document):
meal_info = mg.EmbeddedDocumentField(MealInfo, required=True)

# https://docs.mongoengine.org/apireference.html#mongoengine.fields.ReferenceField
onsite_staff = mg.ListField(
onsite_contacts = mg.ListField(
mg.ReferenceField(OnsiteContact, required=True, reverse_delete_rule=4)
) # 4 = PULL
date_created = mg.DateTimeField(required=True, default=datetime.utcnow)
Expand All @@ -53,15 +58,26 @@ class MealRequest(mg.Document):
donation_info = mg.EmbeddedDocumentField(DonationInfo, default=None)

def validate_onsite_contacts(self):
if self.onsite_staff:
if self.onsite_contacts:
# Try to fetch the referenced document to ensure it exists, will throw an error if it doesn't
for contact in self.onsite_staff:
for contact in self.onsite_contacts:
contact = OnsiteContact.objects(id=contact.id).first()
if not contact or contact.organization_id != self.requestor.id:
raise Exception(
f"onsite contact {contact.id} not found or not associated with the requestor's organization"
)

if self.donation_info:
for contact in self.donation_info.donor_onsite_contacts:
contact = OnsiteContact.objects(id=contact.id).first()
if (
not contact
or contact.organization_id != self.donation_info.donor.id
):
raise Exception(
f"onsite contact {contact.id} not found or not associated with the donor organization"
)

def to_serializable_dict(self):
"""
Returns a dict representation of the document that is JSON serializable
Expand All @@ -71,11 +87,42 @@ def to_serializable_dict(self):
meal_request_dict = self.to_mongo().to_dict()
id = meal_request_dict.pop("_id", None)
meal_request_dict["id"] = str(id)
contacts = [contact.to_mongo().to_dict() for contact in self.onsite_staff]

contacts = [contact.to_mongo().to_dict() for contact in self.onsite_contacts]
for contact in contacts:
id = contact.pop("_id")
contact["id"] = id
meal_request_dict["onsite_staff"] = contacts
meal_request_dict["onsite_contacts"] = contacts

if self.donation_info and self.donation_info.donor_onsite_contacts:
contacts = [
contact.to_mongo().to_dict()
for contact in self.donation_info.donor_onsite_contacts
]
for contact in contacts:
id = contact.pop("_id")
contact["id"] = id
meal_request_dict["donation_info"]["donor_onsite_contacts"] = contacts

return meal_request_dict

def to_dto(self):
dict = self.to_serializable_dict()
requestor = User.objects(id=dict["requestor"]).first()

if not requestor:
raise Exception(f'requestor "{self.requestor.id}" not found')

requestor_dict = requestor.to_serializable_dict()
dict["requestor"] = requestor_dict

if "donation_info" in dict:
donor_id = dict["donation_info"]["donor"]
donor = User.objects(id=donor_id).first()
if not donor:
raise Exception(f'donor "{donor_id}" not found')
dict["donation_info"]["donor"] = donor.to_serializable_dict()

return MealRequestDTO(**dict)

meta = {"collection": "meal_requests"}
12 changes: 7 additions & 5 deletions backend/app/resources/meal_request_dto.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime

from ..models.meal_request import MEAL_STATUSES_STRINGS

from .validate_utils import (
validate_contact,
validate_donation_info,
Expand All @@ -18,7 +18,7 @@ def __init__(
drop_off_datetime,
drop_off_location,
meal_info,
onsite_staff,
onsite_contacts,
date_created,
date_updated,
delivery_instructions=None,
Expand All @@ -30,7 +30,7 @@ def __init__(
self.drop_off_datetime = drop_off_datetime
self.drop_off_location = drop_off_location
self.meal_info = meal_info
self.onsite_staff = onsite_staff
self.onsite_contacts = onsite_contacts
self.date_created = date_created
self.date_updated = date_updated
self.delivery_instructions = delivery_instructions
Expand All @@ -42,6 +42,8 @@ def __init__(
raise Exception(error_message)

def validate(self):
from app.models.meal_request import MEAL_STATUSES_STRINGS

error_list = []

if type(self.id) is not str:
Expand Down Expand Up @@ -71,8 +73,8 @@ def validate(self):

validate_meal_info(self.meal_info, error_list)

for i, staff in enumerate(self.onsite_staff):
validate_contact(staff, f"index {i} of onsite_staff", error_list)
for i, staff in enumerate(self.onsite_contacts):
validate_contact(staff, f"index {i} of onsite_contacts", error_list)

if type(self.date_created) is not datetime.datetime:
error_list.append("The date_created supplied is not a datetime object.")
Expand Down
14 changes: 14 additions & 0 deletions backend/app/resources/validate_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def validate_role_info(role, role_info, role_info_str, error_list):
if role == UserInfoRole.ASP.value:
for field in asp_info_fields:
role_info = role_info["asp_info"]
if not role_info:
error_list.append(
f"The {role_info_str} supplied does not have asp_info even though user is an ASP!."
)
return

if field not in role_info:
error_list.append(
f'The {role_info_str} supplied does not have field "{field}".'
Expand Down Expand Up @@ -162,6 +168,7 @@ def validate_donation_info(donation_info, error_list):
"commitment_date",
"meal_description",
"additional_info",
"donor_onsite_contacts",
]

if not isinstance(donation_info, dict):
Expand All @@ -188,3 +195,10 @@ def validate_donation_info(donation_info, error_list):
error_list.append("The meal_description supplied is not a string.")
elif key == "additional_info" and type(val) is not str:
error_list.append("The additional_info supplied is not a string.")
elif key == "donor_onsite_contacts" and type(val) is not list:
error_list.append("The donor_onsite_contacts supplied is not list.")
for s in val:
if type(s) is not str:
error_list.append(
"The donor_onsite_contacts supplied is not list of strings."
)
Loading

0 comments on commit c44fa4d

Please sign in to comment.