Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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: 6 additions & 0 deletions portal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,9 @@

MEDIA_URL = "/media/" # URL to serve media files
MEDIA_ROOT = os.path.join(BASE_DIR, "media") # Local filesystem path

# Google Drive Settings
GOOGLE_SERVICE_ACCOUNT_FILE = os.path.join(
BASE_DIR, "krishnapachauri-208910-574aa1e639b3.json"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use env var instead of hardcoding

)
GOOGLE_DRIVE_FOLDER_ID = "18P_t0wpEXuMQyQkZJxccsacOh-eelLDk"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use env var instead of hardcoding

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I’ve make it a test version to verify the logic is correct or not, will fix it once we agree that this is what we gonna do.

4 changes: 3 additions & 1 deletion requirements-app.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ whitenoise==6.9.0
dj-database-url==2.3.0
boto3==1.38.5
django-storages==1.14.6
pillow==11.2.1
pillow==11.2.1
google-auth>=1.0.0
google-api-python-client>=2.0.0
6 changes: 6 additions & 0 deletions volunteer/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib import admin

from .constants import ApplicationStatus
from .models import Role, Team, VolunteerProfile


Expand All @@ -8,6 +9,11 @@ class VolunteerProfileAdmin(admin.ModelAdmin):
search_fields = ("user__email", "user__first_name", "user__last_name")
list_filter = ("timezone", "application_status")

def approve_volunteers(self, request, queryset):
for profile in queryset:
profile.application_status = ApplicationStatus.APPROVED
profile.save()


admin.site.register(Role)
admin.site.register(Team)
Expand Down
3 changes: 3 additions & 0 deletions volunteer/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class VolunteerConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "volunteer"

def ready(self):
import volunteer.signals # noqa: F401
120 changes: 120 additions & 0 deletions volunteer/gdrive_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import logging

from django.conf import settings
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

logger = logging.getLogger(__name__)

SCOPES = ["https://www.googleapis.com/auth/drive"]
SERVICE_ACCOUNT_FILE = settings.GOOGLE_SERVICE_ACCOUNT_FILE
FOLDER_ID = settings.GOOGLE_DRIVE_FOLDER_ID


def validate_folder_id():
service = get_gdrive_service()
try:
service.files().get(fileId=FOLDER_ID, fields="id,name").execute()
return True
except HttpError as e:
if e.resp.status == 404:
logger.error(
f"Folder ID {FOLDER_ID} not found. Please check the folder ID in settings."
)
else:
logger.error(f"Error validating folder ID: {str(e)}")
return False
except Exception as e:
logger.error(f"Unexpected error validating folder ID: {str(e)}")
return False


def get_gdrive_service():
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
return build("drive", "v3", credentials=credentials)


def add_to_gdrive(email):
service = get_gdrive_service()
try:
if not validate_folder_id():
logger.error(f"Cannot add {email} to GDrive: Invalid folder ID")
return

permissions = (
service.permissions()
.list(fileId=FOLDER_ID, fields="permissions(id, emailAddress)")
.execute()
.get("permissions", [])
)

existing = [p for p in permissions if p.get("emailAddress") == email]

if existing:
logger.info(f"Email {email} already has access.")
return

permission = {"type": "user", "role": "writer", "emailAddress": email}

result = (
service.permissions()
.create(
fileId=FOLDER_ID,
body=permission,
fields="id",
sendNotificationEmail=False,
)
.execute()
)

logger.info(f"Added {email} to GDrive with permission ID: {result.get('id')}")

except HttpError as e:
if e.resp.status == 404:
logger.error(
f"Google API error adding {email}: Folder not found. Check GOOGLE_DRIVE_FOLDER_ID in settings."
)
else:
logger.error(f"Google API error adding {email}: {str(e)}")
except Exception as e:
logger.error(f"Error adding {email} to GDrive: {str(e)}")


def remove_from_gdrive(email):
service = get_gdrive_service()
try:
if not validate_folder_id():
logger.error(f"Cannot remove {email} from GDrive: Invalid folder ID")
return

permissions = (
service.permissions()
.list(fileId=FOLDER_ID, fields="permissions(id, emailAddress)")
.execute()
.get("permissions", [])
)

target_permissions = [p for p in permissions if p.get("emailAddress") == email]

if not target_permissions:
logger.info(f"No permissions found for {email} to remove")
return

for p in target_permissions:
service.permissions().delete(
fileId=FOLDER_ID, permissionId=p["id"]
).execute()
logger.info(f"Removed {email} from GDrive (permission ID: {p['id']})")

except HttpError as e:
if e.resp.status == 404:
logger.error(
f"Google API error removing {email}: Folder not found. Check GOOGLE_DRIVE_FOLDER_ID in settings."
)
else:
logger.error(f"Google API error removing {email}: {str(e)}")
except Exception as e:
logger.error(f"Error removing {email} from GDrive: {str(e)}")
34 changes: 34 additions & 0 deletions volunteer/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

from .constants import ApplicationStatus
from .gdrive_utils import add_to_gdrive, remove_from_gdrive
from .models import VolunteerProfile


@receiver(pre_save, sender=VolunteerProfile)
def track_approval_status(sender, instance, **kwargs):
"""Capture previous status before save"""
try:
original = VolunteerProfile.objects.get(pk=instance.pk)
instance._original_application_status = original.application_status
except VolunteerProfile.DoesNotExist:
instance._original_application_status = None


@receiver(post_save, sender=VolunteerProfile)
def handle_approval_status(sender, instance, **kwargs):
"""Handle GDrive access based on status changes"""
original_status = instance._original_application_status
new_status = instance.application_status

email = instance.user.email

if new_status == ApplicationStatus.APPROVED:
# New approval or re-approval
if original_status != ApplicationStatus.APPROVED and email:
add_to_gdrive(email)
else:
# Revoked approval
if original_status == ApplicationStatus.APPROVED and email:
remove_from_gdrive(email)
Loading