Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pedantic renaming for consistency with database #1114

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions src/backend/app/auth/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async def org_admin(
db: Session = Depends(get_db),
user_data: AuthUser = Depends(login_required),
) -> AuthUser:
"""Organization admin with full permission for projects in an organization."""
"""Organisation admin with full permission for projects in an organisation."""
if project and org_id:
log.error("Both org_id and project_id cannot be passed at the same time")
raise HTTPException(
Expand All @@ -89,10 +89,10 @@ async def org_admin(
)

if not org_admin:
log.error(f"User ID {user_id} is not an admin for organization {org_id}")
log.error(f"User ID {user_id} is not an admin for organisation {org_id}")
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="User is not organization admin",
detail="User is not organisation admin",
)

return user_data
Expand Down
6 changes: 3 additions & 3 deletions src/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
from app.central import central_routes
from app.config import settings
from app.db.database import get_db
from app.organization import organization_routes
from app.organisations import organisation_routes
from app.projects import project_routes
from app.projects.project_crud import read_xlsforms
from app.submission import submission_routes
from app.submissions import submission_routes
from app.tasks import tasks_routes
from app.users import user_routes

Expand Down Expand Up @@ -95,7 +95,7 @@ def get_application() -> FastAPI:
_app.include_router(central_routes.router)
_app.include_router(auth_routes.router)
_app.include_router(submission_routes.router)
_app.include_router(organization_routes.router)
_app.include_router(organisation_routes.router)

return _app

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with FMTM. If not, see <https:#www.gnu.org/licenses/>.
#
"""Logic for organization management."""
"""Logic for organisation management."""

from io import BytesIO

Expand All @@ -27,10 +27,10 @@
from app.config import settings
from app.db import db_models
from app.models.enums import HTTPStatus
from app.organization.organization_deps import (
get_organization_by_name,
from app.organisations.organisation_deps import (
get_organisation_by_name,
)
from app.organization.organization_schemas import OrganisationEdit, OrganisationIn
from app.organisations.organisation_schemas import OrganisationEdit, OrganisationIn
from app.s3 import add_obj_to_bucket


Expand All @@ -50,7 +50,7 @@ async def upload_logo_to_s3(
so it should not matter if a .jpg is renamed .png.

Args:
db_org(db_models.DbOrganisation): The organization database object.
db_org(db_models.DbOrganisation): The organisation database object.
logo_file(UploadFile): The logo image uploaded to FastAPI.

Returns:
Expand All @@ -73,116 +73,116 @@ async def upload_logo_to_s3(
return logo_url


async def create_organization(
async def create_organisation(
db: Session, org_model: OrganisationIn, logo: UploadFile(None)
) -> db_models.DbOrganisation:
"""Creates a new organization with the given name, description, url, type, and logo.
"""Creates a new organisation with the given name, description, url, type, and logo.

Saves the logo file S3 bucket under /{org_id}/logo.png.

Args:
db (Session): database session
org_model (OrganisationIn): Pydantic model for organization input.
logo (UploadFile, optional): logo file of the organization.
org_model (OrganisationIn): Pydantic model for organisation input.
logo (UploadFile, optional): logo file of the organisation.
Defaults to File(...).

Returns:
DbOrganization: SQLAlchemy Organization model.
DbOrganisation: SQLAlchemy Organisation model.
"""
if await get_organization_by_name(db, org_name=org_model.name):
if await get_organisation_by_name(db, org_name=org_model.name):
raise HTTPException(
status_code=HTTPStatus.CONFLICT,
detail=f"Organization already exists with the name {org_model.name}",
detail=f"Organisation already exists with the name {org_model.name}",
)

# Required to check if exists on error
db_organization = None
db_organisation = None

try:
# Create new organization without logo set
db_organization = db_models.DbOrganisation(**org_model.dict())
# Create new organisation without logo set
db_organisation = db_models.DbOrganisation(**org_model.dict())

db.add(db_organization)
db.add(db_organisation)
db.commit()
# Refresh to get the assigned org id
db.refresh(db_organization)
db.refresh(db_organisation)

# Update the logo field in the database with the correct path
if logo:
db_organization.logo = await upload_logo_to_s3(db_organization, logo)
db_organisation.logo = await upload_logo_to_s3(db_organisation, logo)
db.commit()

except Exception as e:
log.exception(e)
log.debug("Rolling back changes to db organization")
log.debug("Rolling back changes to db organisation")
# Rollback any changes
db.rollback()
# Delete the failed organization entry
if db_organization:
log.debug(f"Deleting created organisation ID {db_organization.id}")
db.delete(db_organization)
# Delete the failed organisation entry
if db_organisation:
log.debug(f"Deleting created organisation ID {db_organisation.id}")
db.delete(db_organisation)
db.commit()
raise HTTPException(
status_code=400, detail=f"Error creating organization: {e}"
status_code=400, detail=f"Error creating organisation: {e}"
) from e

return db_organization
return db_organisation


async def update_organization(
async def update_organisation(
db: Session,
organization: db_models.DbOrganisation,
organisation: db_models.DbOrganisation,
values: OrganisationEdit,
logo: UploadFile(None),
) -> db_models.DbOrganisation:
"""Update an existing organisation database entry.

Args:
db (Session): database session
organization (DbOrganisation): Editing database model.
values (OrganisationEdit): Pydantic model for organization edit.
logo (UploadFile, optional): logo file of the organization.
organisation (DbOrganisation): Editing database model.
values (OrganisationEdit): Pydantic model for organisation edit.
logo (UploadFile, optional): logo file of the organisation.
Defaults to File(...).

Returns:
DbOrganization: SQLAlchemy Organization model.
DbOrganisation: SQLAlchemy Organisation model.
"""
if not (updated_fields := values.dict(exclude_none=True)):
raise HTTPException(
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
detail=f"No values were provided to update organization {organization.id}",
detail=f"No values were provided to update organisation {organisation.id}",
)

update_cmd = (
update(db_models.DbOrganisation)
.where(db_models.DbOrganisation.id == organization.id)
.where(db_models.DbOrganisation.id == organisation.id)
.values(**updated_fields)
)
db.execute(update_cmd)

if logo:
organization.logo = await upload_logo_to_s3(organization, logo)
organisation.logo = await upload_logo_to_s3(organisation, logo)

db.commit()
db.refresh(organization)
db.refresh(organisation)

return organization
return organisation


async def delete_organization(
async def delete_organisation(
db: Session,
organization: db_models.DbOrganisation,
organisation: db_models.DbOrganisation,
) -> Response:
"""Delete an existing organisation database entry.

Args:
db (Session): database session
organization (DbOrganisation): Database model to delete.
organisation (DbOrganisation): Database model to delete.

Returns:
bool: If deletion was successful.
"""
db.delete(organization)
db.delete(organisation)
db.commit()

return Response(status_code=HTTPStatus.NO_CONTENT)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# along with FMTM. If not, see <https:#www.gnu.org/licenses/>.
#

"""Organization dependencies for use in Depends."""
"""Organisation dependencies for use in Depends."""

from typing import Union

Expand All @@ -31,15 +31,15 @@
from app.models.enums import HTTPStatus


async def get_organization_by_name(db: Session, org_name: str) -> DbOrganisation:
"""Get an organization from the db by name.
async def get_organisation_by_name(db: Session, org_name: str) -> DbOrganisation:
"""Get an organisation from the db by name.

Args:
db (Session): database session
org_name (int): id of the organization
org_name (int): id of the organisation

Returns:
DbOrganisation: organization with the given id
DbOrganisation: organisation with the given id
"""
return (
db.query(DbOrganisation)
Expand All @@ -49,14 +49,14 @@ async def get_organization_by_name(db: Session, org_name: str) -> DbOrganisation


async def get_organisation_by_id(db: Session, org_id: int) -> DbOrganisation:
"""Get an organization from the db by id.
"""Get an organisation from the db by id.

Args:
db (Session): database session
org_id (int): id of the organization
org_id (int): id of the organisation

Returns:
DbOrganisation: organization with the given id
DbOrganisation: organisation with the given id
"""
return db.query(DbOrganisation).filter(DbOrganisation.id == org_id).first()

Expand All @@ -65,7 +65,7 @@ async def org_exists(
org_id: Union[str, int],
db: Session = Depends(get_db),
) -> DbOrganisation:
"""Check if organization name exists, else error.
"""Check if organisation name exists, else error.

The org_id can also be an org name.
"""
Expand All @@ -75,18 +75,18 @@ async def org_exists(
pass

if isinstance(org_id, int):
log.debug(f"Getting organization by id: {org_id}")
db_organization = await get_organisation_by_id(db, org_id)
log.debug(f"Getting organisation by id: {org_id}")
db_organisation = await get_organisation_by_id(db, org_id)

if isinstance(org_id, str):
log.debug(f"Getting organization by name: {org_id}")
db_organization = await get_organization_by_name(db, org_id)
log.debug(f"Getting organisation by name: {org_id}")
db_organisation = await get_organisation_by_name(db, org_id)

if not db_organization:
if not db_organisation:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Organization {org_id} does not exist",
detail=f"Organisation {org_id} does not exist",
)

log.debug(f"Organization match: {db_organization}")
return db_organization
log.debug(f"Organisation match: {db_organisation}")
return db_organisation
87 changes: 87 additions & 0 deletions src/backend/app/organisations/organisation_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright (c) 2022, 2023 Humanitarian OpenStreetMap Team
#
# This file is part of FMTM.
#
# FMTM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FMTM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FMTM. If not, see <https:#www.gnu.org/licenses/>.
#
"""Routes for organisation management."""

from fastapi import (
APIRouter,
Depends,
File,
UploadFile,
)
from sqlalchemy.orm import Session

from app.db import database
from app.db.db_models import DbOrganisation
from app.organisations import organisation_crud, organisation_schemas
from app.organisations.organisation_deps import org_exists

router = APIRouter(
prefix="/organisation",
tags=["organisation"],
dependencies=[Depends(database.get_db)],
responses={404: {"description": "Not found"}},
)


@router.get("/", response_model=list[organisation_schemas.OrganisationOut])
def get_organisations(
db: Session = Depends(database.get_db),
) -> list[organisation_schemas.OrganisationOut]:
"""Get a list of all organisations."""
return organisation_crud.get_organisations(db)


@router.get("/{org_id}", response_model=organisation_schemas.OrganisationOut)
async def get_organisation_detail(
organisation: DbOrganisation = Depends(org_exists),
db: Session = Depends(database.get_db),
):
"""Get a specific organisation by id or name."""
return organisation


@router.post("/", response_model=organisation_schemas.OrganisationOut)
async def create_organisation(
org: organisation_schemas.OrganisationIn = Depends(),
logo: UploadFile = File(None),
db: Session = Depends(database.get_db),
) -> organisation_schemas.OrganisationOut:
"""Create an organisation with the given details."""
return await organisation_crud.create_organisation(db, org, logo)


@router.patch("/{org_id}/", response_model=organisation_schemas.OrganisationOut)
async def update_organisation(
new_values: organisation_schemas.OrganisationEdit = Depends(),
logo: UploadFile = File(None),
organisation: DbOrganisation = Depends(org_exists),
db: Session = Depends(database.get_db),
):
"""Partial update for an existing organisation."""
return await organisation_crud.update_organisation(
db, organisation, new_values, logo
)


@router.delete("/{org_id}")
async def delete_organisations(
organisation: DbOrganisation = Depends(org_exists),
db: Session = Depends(database.get_db),
):
"""Delete an organisation."""
return await organisation_crud.delete_organisation(db, organisation)
Loading