RaktaPulse
This commit is contained in:
parent
924c47dea2
commit
24205f808f
49
README.md
49
README.md
@ -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.*
|
||||
|
||||
Binary file not shown.
@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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'}),
|
||||
|
||||
@ -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)
|
||||
|
||||
75
core/urls.py
75
core/urls.py
@ -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"),
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
BIN
media/chat_attachments/Garden_City.jpg
Normal file
BIN
media/chat_attachments/Garden_City.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
media/chat_attachments/cleanliness_drive_.jpg
Normal file
BIN
media/chat_attachments/cleanliness_drive_.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user