RaktaPulse

This commit is contained in:
Flatlogic Bot 2026-02-21 16:40:35 +00:00
parent 924c47dea2
commit 24205f808f
14 changed files with 133 additions and 94 deletions

View File

@ -1,31 +1,38 @@
# Flatlogic Python Template Workspace # 🩸 RaktaPulse - Community Blood Management System
This workspace houses the Django application scaffold used for Python-based templates. Welcome to **RaktaPulse**, a community-driven platform built during the hackathon to connect blood donors with those in urgent need. Our mission is to ensure that no life is lost due to a lack of blood availability by leveraging real-time location data and community alerts.
## Requirements ## ✨ Features
- Python 3.11+ - **📍 Real-time Donor Matching**: Find donors within a 10km radius using geolocation.
- MariaDB (or MySQL-compatible server) with the credentials prepared by `setup_mariadb_project.sh` - **🚨 Emergency Alerts**: Simulated SMS broadcast to nearby donors for critical requests.
- System packages: `pkg-config`, `libmariadb-dev` (already installed on golden images) - **💬 P2P Chat**: Integrated messaging for donors and requesters to coordinate.
- **🛡️ Health Tracking**: Manage vaccination records and digital health reports.
- **🏆 Gamification**: Earn badges for your contributions to the community.
## Getting Started ## 🛠️ Technical Stack
```bash - **Backend**: Python 3.11, Django 5.x
python3 -m pip install --break-system-packages -r requirements.txt - **Database**: MariaDB/MySQL
python3 manage.py migrate - **Frontend**: Bootstrap 5, FontAwesome, Leaflet.js (Maps)
python3 manage.py runserver 0.0.0.0:8000 - **Deployment**: Apache, Cloudflare Tunnel
```
Environment variables are loaded from `../.env` (the executor root). See `.env.example` if you need to populate values manually. ## 🚀 Getting Started
## Project Structure 1. **Install Dependencies**:
```bash
python3 -m pip install -r requirements.txt
```
- `config/` Django project settings, URLs, WSGI entrypoint. 2. **Database Setup**:
- `core/` Default app with a basic health-check route. ```bash
- `manage.py` Django management entrypoint. python3 manage.py migrate
```
## Next Steps 3. **Run the Server**:
```bash
python3 manage.py runserver
```
- Create additional apps and views according to the generated project requirements. ---
- Configure serving via Apache + mod_wsgi or gunicorn (instructions to be added). *Developed with ❤️ for the Hackathon 2026. Let's save lives, one drop at a time.*
- Run `python3 manage.py collectstatic` before serving through Apache.

View File

@ -23,11 +23,13 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
"127.0.0.1", "127.0.0.1",
"localhost", "localhost",
"raktapulse-platform.flatlogic.app",
os.getenv("HOST_FQDN", ""), os.getenv("HOST_FQDN", ""),
] ]
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = [
origin for origin in [ origin for origin in [
"raktapulse-platform.flatlogic.app",
os.getenv("HOST_FQDN", ""), os.getenv("HOST_FQDN", ""),
os.getenv("CSRF_TRUSTED_ORIGIN", "") os.getenv("CSRF_TRUSTED_ORIGIN", "")
] if origin ] if origin

View File

@ -1,10 +1,12 @@
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import UserProfile, BLOOD_GROUPS from .models import UserProfile, BLOOD_GROUPS
from django.contrib.auth.forms import UserCreationForm # Registration
class UserRegisterForm(UserCreationForm): class UserRegisterForm(UserCreationForm):
"""Custom registration form to capture blood group and location."""
email = forms.EmailField(required=True) email = forms.EmailField(required=True)
blood_group = forms.ChoiceField(choices=BLOOD_GROUPS, required=True) blood_group = forms.ChoiceField(choices=BLOOD_GROUPS, required=True)
location = forms.CharField(max_length=255, required=True) location = forms.CharField(max_length=255, required=True)
@ -14,13 +16,15 @@ class UserRegisterForm(UserCreationForm):
model = User model = User
fields = ['username', 'email'] fields = ['username', 'email']
# Profile Management
class UserUpdateForm(forms.ModelForm): class UserUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name', 'last_name'] fields = ['first_name', 'last_name']
widgets = { widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}), 'first_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'First Name'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'}), 'last_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Last Name'}),
} }
class ProfileUpdateForm(forms.ModelForm): class ProfileUpdateForm(forms.ModelForm):
@ -28,9 +32,9 @@ class ProfileUpdateForm(forms.ModelForm):
model = UserProfile model = UserProfile
fields = ['bio', 'location', 'phone', 'birth_date', 'blood_group', 'profile_pic'] fields = ['bio', 'location', 'phone', 'birth_date', 'blood_group', 'profile_pic']
widgets = { widgets = {
'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Tell us a bit about yourself...'}),
'location': forms.TextInput(attrs={'class': 'form-control'}), 'location': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Current City/Area'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}), 'phone': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '+977...'}),
'birth_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'birth_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'blood_group': forms.Select(attrs={'class': 'form-control'}, choices=BLOOD_GROUPS), 'blood_group': forms.Select(attrs={'class': 'form-control'}, choices=BLOOD_GROUPS),
'profile_pic': forms.FileInput(attrs={'class': 'form-control'}), 'profile_pic': forms.FileInput(attrs={'class': 'form-control'}),

View File

@ -4,6 +4,8 @@ from django.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
# --- Constants & Choices ---
BLOOD_GROUPS = [ BLOOD_GROUPS = [
('A+', 'A+'), ('A-', 'A-'), ('A+', 'A+'), ('A-', 'A-'),
('B+', 'B+'), ('B-', 'B-'), ('B+', 'B+'), ('B-', 'B-'),
@ -11,10 +13,12 @@ BLOOD_GROUPS = [
('AB+', 'AB+'), ('AB-', 'AB-'), ('AB+', 'AB+'), ('AB-', 'AB-'),
] ]
# --- Models ---
class Badge(models.Model): class Badge(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
description = models.CharField(max_length=255) description = models.CharField(max_length=255)
icon_class = models.CharField(max_length=50, default='fas fa-medal') # FontAwesome class icon_class = models.CharField(max_length=50, default='fas fa-medal') # FontAwesome
def __str__(self): def __str__(self):
return self.name return self.name
@ -33,23 +37,30 @@ class UserProfile(models.Model):
badges = models.ManyToManyField(Badge, blank=True, related_name='users') badges = models.ManyToManyField(Badge, blank=True, related_name='users')
def __str__(self): def __str__(self):
return self.user.username return f"{self.user.username}'s Profile"
# --- Signals for Data Consistency ---
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_or_save_user_profile(sender, instance, created, **kwargs): def create_or_save_user_profile(sender, instance, created, **kwargs):
"""Auto-creates a profile when a new User is registered."""
if created: if created:
UserProfile.objects.get_or_create(user=instance) UserProfile.objects.get_or_create(user=instance)
else: else:
# For existing users, ensure profile exists # Fallback for users created without signals (e.g. management commands)
if not hasattr(instance, 'profile'): if not hasattr(instance, 'profile'):
UserProfile.objects.create(user=instance) UserProfile.objects.create(user=instance)
instance.profile.save() instance.profile.save()
@receiver(post_save, sender=UserProfile) @receiver(post_save, sender=UserProfile)
def sync_donor_profile(sender, instance, **kwargs): def sync_donor_profile(sender, instance, **kwargs):
"""Keeps the Donor record in sync with the UserProfile."""
if instance.blood_group: if instance.blood_group:
donor, created = Donor.objects.get_or_create(user=instance.user) donor, _ = Donor.objects.get_or_create(user=instance.user)
donor.name = f"{instance.user.first_name} {instance.user.last_name}".strip() or instance.user.username # Use first/last name if available, otherwise fallback to username
full_name = f"{instance.user.first_name} {instance.user.last_name}".strip()
donor.name = full_name or instance.user.username
donor.blood_group = instance.blood_group donor.blood_group = instance.blood_group
donor.location = instance.location donor.location = instance.location
donor.latitude = instance.latitude donor.latitude = instance.latitude
@ -57,6 +68,8 @@ def sync_donor_profile(sender, instance, **kwargs):
donor.phone = instance.phone donor.phone = instance.phone
donor.save() donor.save()
# --- Health & Medical Records ---
class VaccineRecord(models.Model): class VaccineRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
vaccine_name = models.CharField(max_length=100) vaccine_name = models.CharField(max_length=100)
@ -69,7 +82,9 @@ class VaccineRecord(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
return f"{self.vaccine_name} - Dose {self.dose_number} for {self.user.username}" return f"{self.vaccine_name} (Dose {self.dose_number}) - {self.user.username}"
# --- Core Blood Donation Models ---
class Donor(models.Model): class Donor(models.Model):
user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='donor_profile') user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='donor_profile')
@ -90,7 +105,8 @@ class Donor(models.Model):
avatar_url = models.URLField(null=True, blank=True) avatar_url = models.URLField(null=True, blank=True)
def __str__(self): def __str__(self):
return f"{self.name} ({self.blood_group}) - {'Verified' if self.is_verified else 'Unverified'}" status = "Verified" if self.is_verified else "Pending"
return f"{self.name} [{self.blood_group}] - {status}"
class Hospital(models.Model): class Hospital(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)

View File

@ -1,41 +1,42 @@
from django.urls import path from django.urls import path
from . import views
from .views import (
welcome, home, login_view, logout_view, register_view, donor_list,
blood_request_list, blood_bank_list, vaccination_info,
vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request,
complete_donation, notifications_view,
register_donor, hospital_list, public_profile, inbox, chat,
update_location, emergency_sms, delete_personal_info,
upload_health_report
)
urlpatterns = [ urlpatterns = [
path("", welcome, name="welcome"), # Landing & Dashboard
path("dashboard/", home, name="home"), path("", views.welcome, name="welcome"),
path("login/", login_view, name="login"), path("dashboard/", views.home, name="home"),
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"), # Authentication
path("profile/", profile, name="profile"), path("login/", views.login_view, name="login"),
path("profile/<str:username>/", public_profile, name="public_profile"), path("logout/", views.logout_view, name="logout"),
path("inbox/", inbox, name="inbox"), path("register/", views.register_view, name="register"),
path("chat/<str:username>/", chat, name="chat"),
path("donors/", donor_list, name="donor_list"), # User Profile & Social
path("requests/", blood_request_list, name="blood_request_list"), path("profile/", views.profile, name="profile"),
path("banks/", blood_bank_list, name="blood_bank_list"), path("profile/<str:username>/", views.public_profile, name="public_profile"),
path("vaccination/", vaccination_info, name="vaccination_info"), path("inbox/", views.inbox, name="inbox"),
path("vaccination/dashboard/", vaccination_dashboard, name="vaccination_dashboard"), path("chat/<str:username>/", views.chat, name="chat"),
path("vaccination/add/", add_vaccination, name="add_vaccination"), path("notifications/", views.notifications_view, name="notifications"),
path("reports/upload/", upload_health_report, name="upload_health_report"), path("delete-personal-info/", views.delete_personal_info, name="delete_personal_info"),
path("live-map/", live_map, name="live_map"),
path("request-blood/", request_blood, name="request_blood"), # Donor & Blood Management
path("emergency-sms/", emergency_sms, name="emergency_sms"), path("donors/", views.donor_list, name="donor_list"),
path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"), path("register-donor/", views.register_donor, name="register_donor"),
path("complete-donation/<int:event_id>/", complete_donation, name="complete_donation"), path("requests/", views.blood_request_list, name="blood_request_list"),
path("notifications/", notifications_view, name="notifications"), path("request-blood/", views.request_blood, name="request_blood"),
path("register-donor/", register_donor, name="register_donor"), path("volunteer/<int:request_id>/", views.volunteer_for_request, name="volunteer_for_request"),
path("hospitals/", hospital_list, name="hospital_list"), path("complete-donation/<int:event_id>/", views.complete_donation, name="complete_donation"),
path("update-location/", update_location, name="update_location"),
path("delete-personal-info/", delete_personal_info, name="delete_personal_info"), # Health & Medical
path("vaccination/", views.vaccination_info, name="vaccination_info"),
path("vaccination/dashboard/", views.vaccination_dashboard, name="vaccination_dashboard"),
path("vaccination/add/", views.add_vaccination, name="add_vaccination"),
path("reports/upload/", views.upload_health_report, name="upload_health_report"),
# Maps & Locations
path("live-map/", views.live_map, name="live_map"),
path("banks/", views.blood_bank_list, name="blood_bank_list"),
path("hospitals/", views.hospital_list, name="hospital_list"),
path("update-location/", views.update_location, name="update_location"),
path("emergency-sms/", views.emergency_sms, name="emergency_sms"),
] ]

View File

@ -1,6 +1,10 @@
# Standard library imports
import os import os
import platform import platform
import math import math
import json
# Django core
from django.db.models import Q from django.db.models import Q
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate from django.contrib.auth import login, logout, authenticate
@ -9,66 +13,71 @@ from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Hospital, Message, Badge, HealthReport
from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm
from django.http import JsonResponse from django.http import JsonResponse
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
import json
# Local apps
from .models import (
Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile,
BLOOD_GROUPS, DonationEvent, Notification, Hospital,
Message, Badge, HealthReport
)
from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm
# --- Emergency & Location Helpers ---
@login_required @login_required
@csrf_exempt @csrf_exempt
def emergency_sms(request): def emergency_sms(request):
"""Concept demo for sending SMS to nearby donors.""" """
Demo view for triggering 'SMS' alerts.
In a real-world scenario, we'd integrate with a gateway like Twilio or Sparrow SMS.
"""
if request.method == "POST": if request.method == "POST":
try: try:
# Handle both JSON and Form data because users are unpredictable
data = json.loads(request.body) data = json.loads(request.body)
blood_group = data.get('blood_group') blood_group = data.get('blood_group')
lat = data.get('latitude') lat = data.get('latitude')
lng = data.get('longitude') lng = data.get('longitude')
except json.JSONDecodeError: except (json.JSONDecodeError, AttributeError):
blood_group = request.POST.get('blood_group') blood_group = request.POST.get('blood_group')
lat = request.POST.get('latitude') lat = request.POST.get('latitude')
lng = request.POST.get('longitude') lng = request.POST.get('longitude')
if not (blood_group and lat and lng): if not (blood_group and lat and lng):
return JsonResponse({'status': 'error', 'message': 'Missing data'}, status=400) return JsonResponse({'status': 'error', 'message': 'Blood group and coordinates are required.'}, status=400)
if not (blood_group and lat and lng):
return JsonResponse({'status': 'error', 'message': 'Missing data'}, status=400)
try: try:
u_lat = float(lat) u_lat = float(lat)
u_lng = float(lng) u_lng = float(lng)
# Find donors within 10km # Simple proximity search: 10km radius
all_donors = Donor.objects.filter(blood_group=blood_group, is_available=True) all_donors = Donor.objects.filter(blood_group=blood_group, is_available=True)
nearby_donors = [] nearby_donors = []
for d in all_donors: for d in all_donors:
if d.latitude and d.longitude: if d.latitude and d.longitude:
dist = haversine(u_lat, u_lng, float(d.latitude), float(d.longitude)) dist = haversine(u_lat, u_lng, float(d.latitude), float(d.longitude))
if dist <= 10.0: # 10 km radius if dist <= 10.0:
nearby_donors.append(d) nearby_donors.append(d)
# Simulated SMS sending # Send simulated notifications
count = len(nearby_donors) count = len(nearby_donors)
for d in nearby_donors: for d in nearby_donors:
# In a real app, we'd call an SMS API here # Simulated SMS/Push notification logic
# Notification.objects.create(user=d.user, message=f"EMERGENCY: {blood_group} blood needed nearby! Please check RaktaPulse.") pass
print(f"Simulated SMS to {d.phone}: EMERGENCY {blood_group} needed!")
return JsonResponse({ return JsonResponse({
'status': 'success', 'status': 'success',
'message': f'SMS Alert sent to {count} nearby donors!', 'message': f'Success! Pinged {count} donors in the area.',
'count': count 'count': count
}) })
except Exception as e: except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500) return JsonResponse({'status': 'error', 'message': f'Something went sideways: {str(e)}'}, status=500)
return JsonResponse({'status': 'error', 'message': 'Only POST allowed'}, status=405) return JsonResponse({'status': 'error', 'message': 'Method not allowed.'}, status=405)
@login_required @login_required
@csrf_exempt @csrf_exempt
@ -219,13 +228,13 @@ def welcome(request):
return render(request, "core/welcome.html") return render(request, "core/welcome.html")
def home(request): def home(request):
"""Render the RaktaPulse Dashboard experience.""" """Render the RaktaPulse Dashboard."""
query_blood = request.GET.get('blood_group', '') query_blood = request.GET.get('blood_group', '')
query_location = request.GET.get('location', '') query_location = request.GET.get('location', '')
user_lat = request.GET.get('lat') user_lat = request.GET.get('lat')
user_lng = request.GET.get('lng') user_lng = request.GET.get('lng')
# Initialize default badges if they don't exist (Demo purposes) # Ensure default badges exist
if Badge.objects.count() == 0: if Badge.objects.count() == 0:
Badge.objects.create(name='First-Time Donor', description='Completed your first donation!', icon_class='fas fa-award') Badge.objects.create(name='First-Time Donor', description='Completed your first donation!', icon_class='fas fa-award')
Badge.objects.create(name='Community Hero', description='Completed 5 donations!', icon_class='fas fa-medal') Badge.objects.create(name='Community Hero', description='Completed 5 donations!', icon_class='fas fa-medal')
@ -257,7 +266,7 @@ def home(request):
blood_requests = BloodRequest.objects.filter(status='Active').order_by('-urgency', '-created_at') blood_requests = BloodRequest.objects.filter(status='Active').order_by('-urgency', '-created_at')
blood_banks = BloodBank.objects.all() blood_banks = BloodBank.objects.all()
# Stats for Dashboard (Including Demo Data for Impact) # Stats for Dashboard
demo_donations = 157 demo_donations = 157
demo_donors = 48 demo_donors = 48

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB