Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/development'
Browse files Browse the repository at this point in the history
Merge with development branch
  • Loading branch information
rsavoye committed Jul 4, 2023
2 parents 1edcf52 + 6a0eeea commit 9437257
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 21 deletions.
40 changes: 37 additions & 3 deletions src/backend/app/central/central_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,40 @@ def delete_app_user(
return result


def upload_xform_media(project_id: int, xform_id:str, filespec: str, odk_credentials: dict = None):

title = os.path.basename(os.path.splitext(filespec)[0])

if odk_credentials:
url = odk_credentials["odk_central_url"]
user = odk_credentials["odk_central_user"]
pw = odk_credentials["odk_central_password"]

else:
logger.debug("ODKCentral connection variables not set in function")
logger.debug("Attempting extraction from environment variables")
url = settings.ODK_CENTRAL_URL
user = settings.ODK_CENTRAL_USER
pw = settings.ODK_CENTRAL_PASSWD

try:
xform = OdkForm(url, user, pw)
except Exception as e:
logger.error(e)
raise HTTPException(
status_code=500, detail={"message": "Connection failed to odk central"}
) from e

result = xform.uploadMedia(project_id, title, filespec)
result = xform.publishForm(project_id, title)
return result



def create_odk_xform(
project_id: int, xform_id: str, filespec: str, odk_credentials: dict = None
project_id: int, xform_id: str, filespec: str, odk_credentials: dict = None,
draft: bool = False,
upload_media = True
):
"""Create an XForm on a remote ODK Central server."""
title = os.path.basename(os.path.splitext(filespec)[0])
Expand All @@ -210,14 +242,16 @@ def create_odk_xform(
status_code=500, detail={"message": "Connection failed to odk central"}
) from e

result = xform.createForm(project_id, xform_id, filespec, False)
result = xform.createForm(project_id, xform_id, filespec, draft)

if result != 200 and result != 409:
return result
data = f"/tmp/{title}.geojson"

# This modifies an existing published XForm to be in draft mode.
# An XForm must be in draft mode to upload an attachment.
result = xform.uploadMedia(project_id, title, data)
if upload_media:
result = xform.uploadMedia(project_id, title, data)

result = xform.publishForm(project_id, title)
return result
Expand Down
61 changes: 49 additions & 12 deletions src/backend/app/organization/organization_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
# along with FMTM. If not, see <https:#www.gnu.org/licenses/>.
#
import os
import random
import string
from fastapi import HTTPException, File,UploadFile
from fastapi.logger import logger as logger
import re
Expand All @@ -24,6 +26,7 @@

from ..db import db_models

IMAGEDIR = "app/images/"

# --------------
# ---- CRUD ----
Expand Down Expand Up @@ -57,7 +60,31 @@ async def get_organisation_by_name(db: Session, name: str):
return db_organisation


async def create_organization(db: Session, name: str, description: str, url: str, logo: UploadFile = File(...)):
async def upload_image(db: Session, file: UploadFile(None)):
# Check if file with the same name exists
filename = file.filename
file_path = f"{IMAGEDIR}{filename}"
while os.path.exists(file_path):

# Generate a random character
random_char = ''.join(random.choices(string.ascii_letters + string.digits, k=3))

# Add the random character to the filename
logo_name, extension = os.path.splitext(filename)
filename = f"{logo_name}_{random_char}{extension}"
file_path = f"{IMAGEDIR}{filename}"

# Read the file contents
contents = await file.read()

# Save the file
with open(file_path, "wb") as f:
f.write(contents)

return filename


async def create_organization(db: Session, name: str, description: str, url: str, logo: UploadFile(None)):
"""
Creates a new organization with the given name, description, url, type, and logo.
Saves the logo file to the app/images folder.
Expand All @@ -76,30 +103,40 @@ async def create_organization(db: Session, name: str, description: str, url: str

# create new organization
try:
logo_name = await upload_image(db, logo) if logo else None

db_organization = db_models.DbOrganisation(
name=name,
slug=generate_slug(name),
description=description,
url=url
url=url,
logo=logo_name
)

# Save logo file to app/images folder
# file_path = os.path.join("app/images")
# if not os.path.exists(file_path):
# os.makedirs(file_path)

# with open(os.path.join(file_path, logo.filename), "wb") as file:
# file.write(await logo.read())

db_organization.logo = logo.filename if logo else None
db.add(db_organization)
db.commit()
db.refresh(db_organization)
except Exception as e:
logger.error(e)
raise HTTPException(
status_code=400, detail=f" ----- Error: {e}"
status_code=400, detail=f"Error creating organization: {e}"
) from e

return True


async def get_organisation_by_id(db: Session, id: int):
"""
Get an organization by its id.
Args:
db (Session): database session
id (int): id of the organization
Returns:
DbOrganisation: organization with the given id
"""
db_organization = (
db.query(db_models.DbOrganisation).filter(db_models.DbOrganisation.id == id).first()
)
return db_organization
15 changes: 15 additions & 0 deletions src/backend/app/organization/organization_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,18 @@ async def create_organization(
await organization_crud.create_organization(db, name, description, url, logo)

return {"Message": "Organization Created Successfully."}


@router.delete("/{organization_id}")
async def delete_organisations(
organization_id: int, db: Session = Depends(database.get_db)
):

organization = await organization_crud.get_organisation_by_id(db, organization_id)

if not organization:
raise HTTPException(status_code=404, detail="Organization not found")

db.delete(organization)
db.commit()
return {"Message": "Organization Deleted Successfully."}
41 changes: 41 additions & 0 deletions src/backend/app/projects/project_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -1719,3 +1719,44 @@ def add_features_into_database(
) # 4 is COMPLETED

return True


async def update_project_form(
db: Session,
project_id: int,
form: str,
form_type: str,
):

project = get_project(db, project_id)
category = project.xform_title
project_title = project.project_name_prefix
odk_id = project.odkid

task = table("tasks", column("outline"), column("id"))
where = f"project_id={project_id}"

sql = select(task).where(text(where))
result = db.execute(sql)

form_type = "xls"

xlsform = f"/tmp/custom_form.{form_type}"
with open(xlsform, "wb") as f:
f.write(form)


for poly in result.fetchall():

xform = f"/tmp/{project_title}_{category}_{poly.id}.xml" # This file will store xml contents of an xls form.
outfile = f"/tmp/{project_title}_{category}_{poly.id}.geojson" # This file will store osm extracts

outfile = central_crud.generate_updated_xform(
xlsform, xform, form_type)

# Create an odk xform
result = central_crud.create_odk_xform(
odk_id, poly.id, outfile, None, True, False
)

return True
25 changes: 23 additions & 2 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,29 @@ async def generate_files(
return {"Message": f"{project_id}", "task_id": f"{background_task_id}"}


@router.post("/update-form/{project_id}")
async def update_project_form(
project_id: int,
form: Optional[UploadFile],
db: Session = Depends(database.get_db),
):

file_name = os.path.splitext(form.filename)
file_ext = file_name[1]
allowed_extensions = [".xls"]
if file_ext not in allowed_extensions:
raise HTTPException(status_code=400, detail="Provide a valid .xls file")
contents = await form.read()

form_updated = await project_crud.update_project_form(
db,
project_id,
contents, # Form Contents
file_ext[1:] # File type
)

return form_updated


@router.get("/{project_id}/features", response_model=List[project_schemas.Feature])
def get_project_features(
Expand Down Expand Up @@ -587,5 +610,3 @@ async def add_features(
background_task_id,
)
return True


43 changes: 43 additions & 0 deletions src/backend/app/submission/submission_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,46 @@ def get_submission_points(db: Session, project_id: int, task_id: int = None):
return None
else:
return None


async def get_submission_count_of_a_project(db:Session,
project_id: int):

project_info = project_crud.get_project(db, project_id)

# Return empty list if project is not found
if not project_info:
raise HTTPException(status_code=404, detail="Project not found")

odkid = project_info.odkid
project_name = project_info.project_name_prefix
form_category = project_info.xform_title
project_tasks = project_info.tasks

# ODK Credentials
odk_credentials = project_schemas.ODKCentral(
odk_central_url=project_info.odk_central_url,
odk_central_user=project_info.odk_central_user,
odk_central_password=project_info.odk_central_password,
)

# Get ODK Form with odk credentials from the project.
xform = get_odk_form(odk_credentials)

files = []

task_list = [x.id for x in project_tasks]
for id in task_list:
xml_form_id = f"{project_name}_{form_category}_{id}".split("_")[
2]
file = xform.getSubmissions(
odkid, xml_form_id, None, False, True)
if not file:
json_data = None
else:
json_data = json.loads(file)
json_data_value = json_data.get('value')
if json_data_value:
files.extend(json_data_value)

return len(files)
8 changes: 8 additions & 0 deletions src/backend/app/submission/submission_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,11 @@ async def convert_to_osm(
"""

return await submission_crud.convert_to_osm(db, project_id, task_id)


@router.get("/get-submission-count/{project_id}")
async def get_submission_count(
project_id: int,
db: Session = Depends(database.get_db),
):
return await submission_crud.get_submission_count_of_a_project(db, project_id)
Loading

0 comments on commit 9437257

Please sign in to comment.