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+
- MariaDB (or MySQL-compatible server) with the credentials prepared by `setup_mariadb_project.sh`
- System packages: `pkg-config`, `libmariadb-dev` (already installed on golden images)
- **📍 Real-time Donor Matching**: Find donors within a 10km radius using geolocation.
- **🚨 Emergency Alerts**: Simulated SMS broadcast to nearby donors for critical requests.
- **💬 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
python3 -m pip install --break-system-packages -r requirements.txt
python3 manage.py migrate
python3 manage.py runserver 0.0.0.0:8000
```
- **Backend**: Python 3.11, Django 5.x
- **Database**: MariaDB/MySQL
- **Frontend**: Bootstrap 5, FontAwesome, Leaflet.js (Maps)
- **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.
- `core/` Default app with a basic health-check route.
- `manage.py` Django management entrypoint.
2. **Database Setup**:
```bash
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).
- Run `python3 manage.py collectstatic` before serving through Apache.
---
*Developed with ❤️ for the Hackathon 2026. Let's save lives, one drop at a time.*

View File

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

View File

@ -1,10 +1,12 @@
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import UserProfile, BLOOD_GROUPS
from django.contrib.auth.forms import UserCreationForm
# Registration
class UserRegisterForm(UserCreationForm):
"""Custom registration form to capture blood group and location."""
email = forms.EmailField(required=True)
blood_group = forms.ChoiceField(choices=BLOOD_GROUPS, required=True)
location = forms.CharField(max_length=255, required=True)
@ -14,13 +16,15 @@ class UserRegisterForm(UserCreationForm):
model = User
fields = ['username', 'email']
# Profile Management
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name']
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_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', 'placeholder': 'Last Name'}),
}
class ProfileUpdateForm(forms.ModelForm):
@ -28,9 +32,9 @@ class ProfileUpdateForm(forms.ModelForm):
model = UserProfile
fields = ['bio', 'location', 'phone', 'birth_date', 'blood_group', 'profile_pic']
widgets = {
'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'location': forms.TextInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}),
'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Tell us a bit about yourself...'}),
'location': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Current City/Area'}),
'phone': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '+977...'}),
'birth_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'blood_group': forms.Select(attrs={'class': 'form-control'}, choices=BLOOD_GROUPS),
'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.dispatch import receiver
# --- Constants & Choices ---
BLOOD_GROUPS = [
('A+', 'A+'), ('A-', 'A-'),
('B+', 'B+'), ('B-', 'B-'),
@ -11,10 +13,12 @@ BLOOD_GROUPS = [
('AB+', 'AB+'), ('AB-', 'AB-'),
]
# --- Models ---
class Badge(models.Model):
name = models.CharField(max_length=50)
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):
return self.name
@ -33,23 +37,30 @@ class UserProfile(models.Model):
badges = models.ManyToManyField(Badge, blank=True, related_name='users')
def __str__(self):
return self.user.username
return f"{self.user.username}'s Profile"
# --- Signals for Data Consistency ---
@receiver(post_save, sender=User)
def create_or_save_user_profile(sender, instance, created, **kwargs):
"""Auto-creates a profile when a new User is registered."""
if created:
UserProfile.objects.get_or_create(user=instance)
else:
# For existing users, ensure profile exists
# Fallback for users created without signals (e.g. management commands)
if not hasattr(instance, 'profile'):
UserProfile.objects.create(user=instance)
instance.profile.save()
@receiver(post_save, sender=UserProfile)
def sync_donor_profile(sender, instance, **kwargs):
"""Keeps the Donor record in sync with the UserProfile."""
if instance.blood_group:
donor, created = Donor.objects.get_or_create(user=instance.user)
donor.name = f"{instance.user.first_name} {instance.user.last_name}".strip() or instance.user.username
donor, _ = Donor.objects.get_or_create(user=instance.user)
# 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.location = instance.location
donor.latitude = instance.latitude
@ -57,6 +68,8 @@ def sync_donor_profile(sender, instance, **kwargs):
donor.phone = instance.phone
donor.save()
# --- Health & Medical Records ---
class VaccineRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
vaccine_name = models.CharField(max_length=100)
@ -69,7 +82,9 @@ class VaccineRecord(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
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):
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)
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):
name = models.CharField(max_length=255)

View File

@ -1,41 +1,42 @@
from django.urls import path
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
)
from . import views
urlpatterns = [
path("", welcome, name="welcome"),
path("dashboard/", home, name="home"),
path("login/", login_view, name="login"),
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"),
path("profile/", profile, name="profile"),
path("profile/<str:username>/", public_profile, name="public_profile"),
path("inbox/", inbox, name="inbox"),
path("chat/<str:username>/", chat, name="chat"),
path("donors/", donor_list, name="donor_list"),
path("requests/", blood_request_list, name="blood_request_list"),
path("banks/", blood_bank_list, name="blood_bank_list"),
path("vaccination/", vaccination_info, name="vaccination_info"),
path("vaccination/dashboard/", vaccination_dashboard, name="vaccination_dashboard"),
path("vaccination/add/", add_vaccination, name="add_vaccination"),
path("reports/upload/", upload_health_report, name="upload_health_report"),
path("live-map/", live_map, name="live_map"),
path("request-blood/", request_blood, name="request_blood"),
path("emergency-sms/", emergency_sms, name="emergency_sms"),
path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"),
path("complete-donation/<int:event_id>/", complete_donation, name="complete_donation"),
path("notifications/", notifications_view, name="notifications"),
path("register-donor/", register_donor, name="register_donor"),
path("hospitals/", hospital_list, name="hospital_list"),
path("update-location/", update_location, name="update_location"),
path("delete-personal-info/", delete_personal_info, name="delete_personal_info"),
# Landing & Dashboard
path("", views.welcome, name="welcome"),
path("dashboard/", views.home, name="home"),
# Authentication
path("login/", views.login_view, name="login"),
path("logout/", views.logout_view, name="logout"),
path("register/", views.register_view, name="register"),
# User Profile & Social
path("profile/", views.profile, name="profile"),
path("profile/<str:username>/", views.public_profile, name="public_profile"),
path("inbox/", views.inbox, name="inbox"),
path("chat/<str:username>/", views.chat, name="chat"),
path("notifications/", views.notifications_view, name="notifications"),
path("delete-personal-info/", views.delete_personal_info, name="delete_personal_info"),
# Donor & Blood Management
path("donors/", views.donor_list, name="donor_list"),
path("register-donor/", views.register_donor, name="register_donor"),
path("requests/", views.blood_request_list, name="blood_request_list"),
path("request-blood/", views.request_blood, name="request_blood"),
path("volunteer/<int:request_id>/", views.volunteer_for_request, name="volunteer_for_request"),
path("complete-donation/<int:event_id>/", views.complete_donation, name="complete_donation"),
# 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 platform
import math
import json
# Django core
from django.db.models import Q
from django.shortcuts import render, redirect
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.utils import timezone
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.views.decorators.http import require_POST
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
@csrf_exempt
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":
try:
# Handle both JSON and Form data because users are unpredictable
data = json.loads(request.body)
blood_group = data.get('blood_group')
lat = data.get('latitude')
lng = data.get('longitude')
except json.JSONDecodeError:
except (json.JSONDecodeError, AttributeError):
blood_group = request.POST.get('blood_group')
lat = request.POST.get('latitude')
lng = request.POST.get('longitude')
if not (blood_group and lat and lng):
return JsonResponse({'status': 'error', 'message': 'Missing data'}, status=400)
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)
try:
u_lat = float(lat)
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)
nearby_donors = []
for d in all_donors:
if d.latitude and 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)
# Simulated SMS sending
# Send simulated notifications
count = len(nearby_donors)
for d in nearby_donors:
# In a real app, we'd call an SMS API here
# Notification.objects.create(user=d.user, message=f"EMERGENCY: {blood_group} blood needed nearby! Please check RaktaPulse.")
print(f"Simulated SMS to {d.phone}: EMERGENCY {blood_group} needed!")
# Simulated SMS/Push notification logic
pass
return JsonResponse({
'status': 'success',
'message': f'SMS Alert sent to {count} nearby donors!',
'message': f'Success! Pinged {count} donors in the area.',
'count': count
})
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
@csrf_exempt
@ -219,13 +228,13 @@ def welcome(request):
return render(request, "core/welcome.html")
def home(request):
"""Render the RaktaPulse Dashboard experience."""
"""Render the RaktaPulse Dashboard."""
query_blood = request.GET.get('blood_group', '')
query_location = request.GET.get('location', '')
user_lat = request.GET.get('lat')
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:
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')
@ -257,7 +266,7 @@ def home(request):
blood_requests = BloodRequest.objects.filter(status='Active').order_by('-urgency', '-created_at')
blood_banks = BloodBank.objects.all()
# Stats for Dashboard (Including Demo Data for Impact)
# Stats for Dashboard
demo_donations = 157
demo_donors = 48

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB