diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc
index 3c20f11..d1a5bd9 100644
Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ
diff --git a/config/urls.py b/config/urls.py
index 701370b..4321ed9 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -25,6 +25,10 @@ urlpatterns = [
path("", include("core.urls")),
]
+admin.site.site_header = "Blood Bank Management System"
+admin.site.site_title = "Admin Portal"
+admin.site.index_title = "Welcome to Blood Bank Management Portal"
+
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index 7b9a1bb..42ebf60 100644
Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index e2cd584..384084c 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index 7b85a88..50e031b 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/admin.py b/core/admin.py
index 660771f..9e3ca05 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -1,24 +1,90 @@
from django.contrib import admin
-from .models import Donor, BloodRequest, BloodBank, VaccineRecord
+import csv
+from django.http import HttpResponse
+from .models import (
+ Donor, BloodRequest, BloodBank, VaccineRecord,
+ DonationEvent, Hospital, UserProfile, Badge,
+ Notification, Message, HealthReport
+)
+
+def export_as_csv(self, request, queryset):
+ meta = self.model._meta
+ field_names = [field.name for field in meta.fields]
+
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = f'attachment; filename={meta}.csv'
+ writer = csv.writer(response)
+
+ writer.writerow(field_names)
+ for obj in queryset:
+ writer.writerow([getattr(obj, field) for field in field_names])
+
+ return response
+
+export_as_csv.short_description = "Export Selected to CSV"
+
+@admin.register(UserProfile)
+class UserProfileAdmin(admin.ModelAdmin):
+ list_display = ('user', 'blood_group', 'location', 'phone')
+ list_filter = ('blood_group',)
+ search_fields = ('user__username', 'phone', 'location')
+ actions = [export_as_csv]
+
+@admin.register(Badge)
+class BadgeAdmin(admin.ModelAdmin):
+ list_display = ('name', 'description')
@admin.register(VaccineRecord)
class VaccineRecordAdmin(admin.ModelAdmin):
list_display = ('vaccine_name', 'user', 'dose_number', 'date_taken', 'location')
list_filter = ('vaccine_name', 'date_taken')
search_fields = ('vaccine_name', 'user__username', 'location')
+ actions = [export_as_csv]
@admin.register(Donor)
class DonorAdmin(admin.ModelAdmin):
- list_display = ('name', 'blood_group', 'location', 'is_available')
- list_filter = ('blood_group', 'is_available')
+ list_display = ('name', 'blood_group', 'location', 'is_available', 'is_verified')
+ list_filter = ('blood_group', 'is_available', 'is_verified')
+ search_fields = ('name', 'location', 'phone')
+ actions = [export_as_csv]
+
+@admin.register(Hospital)
+class HospitalAdmin(admin.ModelAdmin):
+ list_display = ('name', 'location', 'phone')
search_fields = ('name', 'location')
@admin.register(BloodRequest)
class BloodRequestAdmin(admin.ModelAdmin):
- list_display = ('patient_name', 'blood_group', 'urgency', 'status', 'created_at')
+ list_display = ('patient_name', 'blood_group', 'urgency', 'status', 'hospital', 'created_at')
list_filter = ('blood_group', 'urgency', 'status')
search_fields = ('patient_name', 'hospital')
+ actions = [export_as_csv]
@admin.register(BloodBank)
class BloodBankAdmin(admin.ModelAdmin):
- list_display = ('name', 'location')
+ list_display = ('name', 'location', 'stock_a_plus', 'stock_b_plus', 'stock_o_plus', 'stock_ab_plus')
+ search_fields = ('name', 'location')
+ actions = [export_as_csv]
+
+@admin.register(DonationEvent)
+class DonationEventAdmin(admin.ModelAdmin):
+ list_display = ('donor', 'request', 'date', 'is_completed')
+ list_filter = ('is_completed', 'date')
+ search_fields = ('donor__name', 'request__patient_name')
+ actions = [export_as_csv]
+
+@admin.register(Notification)
+class NotificationAdmin(admin.ModelAdmin):
+ list_display = ('user', 'message', 'is_read', 'created_at')
+ list_filter = ('is_read', 'created_at')
+
+@admin.register(Message)
+class MessageAdmin(admin.ModelAdmin):
+ list_display = ('sender', 'receiver', 'message_type', 'timestamp', 'is_read')
+ list_filter = ('message_type', 'is_read', 'timestamp')
+
+@admin.register(HealthReport)
+class HealthReportAdmin(admin.ModelAdmin):
+ list_display = ('title', 'user', 'hospital_name', 'report_date')
+ list_filter = ('report_date',)
+ search_fields = ('title', 'user__username', 'hospital_name')
diff --git a/core/templates/base.html b/core/templates/base.html
index 1e0e226..0a323df 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -286,6 +286,8 @@
{% trans "Dashboard" %}
{% trans "Donors" %}
{% trans "Blood Requests" %}
+ {% trans "Donation History" %}
+ {% trans "Lives Saved" %}
{% trans "Blood Banks" %}
{% trans "Hospitals" %}
{% trans "Live Alerts" %}
@@ -354,9 +356,11 @@
+ {% if unread_notifications_count > 0 %}
- {{ user.notifications.count }}
+ {{ unread_notifications_count }}
+ {% endif %}
diff --git a/core/templates/core/donation_history.html b/core/templates/core/donation_history.html
index a1030ca..775b4af 100644
--- a/core/templates/core/donation_history.html
+++ b/core/templates/core/donation_history.html
@@ -1,4 +1,4 @@
-{% extends 'core/base.html' %}
+{% extends 'base.html' %}
{% load static %}
{% block content %}
@@ -6,13 +6,47 @@
{{ title }}
-
A transparent record of every life saved through the community's generosity.
+
Explore the complete log of blood donations within the RaktaPulse community.
-
+
Total Impact
{{ donations.count }} Donations
+
+ Export
+
+
+
+
+
+
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index 91940a3..d16a1a1 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -94,6 +94,67 @@
color: white !important;
}
+ .donor-list-container {
+ max-height: 550px;
+ overflow-y: auto;
+ padding-right: 10px;
+ }
+ .donor-list-container::-webkit-scrollbar {
+ width: 6px;
+ }
+ .donor-list-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 10px;
+ }
+ .donor-list-container::-webkit-scrollbar-thumb {
+ background: var(--pulse-red);
+ border-radius: 10px;
+ }
+
+ .hero-section {
+ background: linear-gradient(135deg, #1a1a1a, #2d1b1b);
+ border: 1px solid rgba(255, 215, 0, 0.3);
+ border-radius: 16px;
+ padding: 12px 16px;
+ margin-bottom: 20px;
+ position: relative;
+ }
+ .hero-item {
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 12px;
+ padding: 8px 12px;
+ margin-right: 10px;
+ border: 1px solid rgba(255, 215, 0, 0.1);
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 180px;
+ }
+ .live-pulse {
+ width: 8px;
+ height: 8px;
+ background: #FF4D4D;
+ border-radius: 50%;
+ display: inline-block;
+ margin-right: 5px;
+ box-shadow: 0 0 0 rgba(255, 77, 77, 0.4);
+ animation: pulse 2s infinite;
+ }
+ @keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(255, 77, 77, 0.7); }
+ 70% { box-shadow: 0 0 0 10px rgba(255, 77, 77, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(255, 77, 77, 0); }
+ }
+ .heroes-scroll {
+ display: flex;
+ overflow-x: auto;
+ padding: 5px 0;
+ scrollbar-width: none;
+ }
+ .heroes-scroll::-webkit-scrollbar {
+ display: none;
+ }
+
.filter-btn {
background: #ffffff;
border: 1px solid var(--border-color);
@@ -207,7 +268,7 @@
-
+
{{ stats.total_stock }} / {{ stats.total_capacity }}
+
{% trans "Stock Level" %}
+
@@ -231,7 +292,7 @@
-
Available Donors
+
Hero Community
@@ -239,6 +300,35 @@
+ {% if recent_heroes %}
+
+
+
+
+ Live: Today's Heroes (Last 24h)
+
+
+
+
+
+ {% endif %}
+
@@ -263,59 +353,61 @@
-
- {% for donor in donors %}
-
-
-
- {% if donor.user and donor.user.profile.profile_pic %}
-

-
- {{ donor.blood_group }}
-
- {% else %}
-
{{ donor.blood_group }}
- {% endif %}
+
+
+ {% for donor in donors %}
+
+
+
+ {% if donor.user and donor.user.profile.profile_pic %}
+

+
+ {{ donor.blood_group }}
+
+ {% else %}
+
{{ donor.blood_group }}
+ {% endif %}
+
+
+
+
+ {{ donor.name }}
+ {% if donor.is_verified %}
+
+ {% endif %}
+ {% if donor.distance and donor.distance < 1000 %}
+
+ {{ donor.distance|floatformat:1 }} km
+
+ {% endif %}
+
+
{{ donor.location }}, {{ donor.district }}
+
-
-
-
- {{ donor.name }}
- {% if donor.is_verified %}
-
- {% endif %}
- {% if donor.distance and donor.distance < 1000 %}
-
- {{ donor.distance|floatformat:1 }} km
+
+
+
+ {% if donor.is_available %}Available{% else %}Unavailable{% endif %}
+
+
+
+ {% if donor.user %}
+
+
+
{% endif %}
-
-
{{ donor.location }}, {{ donor.district }}
+
Call
+
-
-
-
- {% if donor.is_available %}Available{% else %}Unavailable{% endif %}
-
-
-
-
- {% if donor.user %}
-
-
-
- {% endif %}
-
Call
-
+ {% empty %}
+
+
+
No donors match your search criteria.
+ {% endfor %}
- {% empty %}
-
-
-
No donors match your search criteria.
-
- {% endfor %}
@@ -370,9 +462,11 @@
+ {% if user.is_staff %}
+ {% endif %}
{% for req in blood_requests %}
@@ -413,7 +507,7 @@
-
+
Blood Bank Inventory
{% for bank in blood_banks %}
@@ -423,17 +517,23 @@
24/7 Available
-
-
-
-
+
+
+
+
+
+
+
+
{% empty %}
No blood banks registered.
{% endfor %}
+ {% if user.is_staff %}
Manage Banks
+ {% endif %}
diff --git a/core/templates/core/lives_saved.html b/core/templates/core/lives_saved.html
new file mode 100644
index 0000000..becbc6a
--- /dev/null
+++ b/core/templates/core/lives_saved.html
@@ -0,0 +1,91 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block content %}
+
+
+
+
{{ title }}
+
Every single donation has the potential to save up to three lives. Together, we are building a safer community.
+
+
+
+
+
+
+
+
+
+
+
{{ total_impact }}
+
Total Lives Saved
+
+
+
+
+
+
+
+
+
+
{{ total_donations }}
+
Successful Donations
+
+
+
+
+
+
+
+
+
+
+
+
+ | Hero |
+ Impact |
+ Location |
+ Date |
+
+
+
+ {% for donation in donations %}
+
+ |
+ {{ donation.donor_user.username }}
+ Verified Donor
+ |
+
+
+ 3 Lives Saved
+
+ |
+
+ {{ donation.request.location }}
+ |
+
+ {{ donation.date|date:"M d, Y" }}
+ |
+
+ {% empty %}
+
+ |
+ No recent events to display.
+ |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/core/urls.py b/core/urls.py
index 3cb4ff1..18c4bf7 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -40,4 +40,5 @@ urlpatterns = [
path("update-location/", views.update_location, name="update_location"),
path("emergency-sms/", views.emergency_sms, name="emergency_sms"),
path("donation-history/", views.donation_history, name="donation_history"),
+ path("lives-saved/", views.lives_saved, name="lives_saved"),
]
diff --git a/core/views.py b/core/views.py
index 736ba8a..9795189 100644
--- a/core/views.py
+++ b/core/views.py
@@ -285,6 +285,13 @@ def home(request):
actual_completed = DonationEvent.objects.filter(is_completed=True).count()
completed_donations = actual_completed + demo_donations
+ # Find Recent Heroes (Last 24 Hours)
+ last_24_hours = timezone.now() - timezone.timedelta(hours=24)
+ recent_heroes = DonationEvent.objects.filter(
+ is_completed=True,
+ date__gte=last_24_hours
+ ).select_related('donor').order_by('-date')
+
stats = {
"total_donors": Donor.objects.count() + demo_donors,
"active_requests": BloodRequest.objects.filter(
@@ -315,11 +322,12 @@ def home(request):
]
context = {
- "donors": donor_list_data[:8],
+ "donors": donor_list_data[:15], # Increased count for scrollability
"blood_requests": blood_requests[:6],
"blood_banks": blood_banks,
"blood_groups": [g[0] for g in BLOOD_GROUPS],
"stats": stats,
+ "recent_heroes": recent_heroes,
"project_name": "RaktaPulse",
"current_time": timezone.now(),
"myths_vs_facts": myths_vs_facts,
@@ -333,6 +341,8 @@ def home(request):
)
context["involved_events"] = involved_events
context["user_badges"] = request.user.profile.badges.all()
+ context["unread_notifications_count"] = request.user.notifications.filter(is_read=False).count()
+ context["unread_messages_count"] = Message.objects.filter(receiver=request.user, is_read=False).count()
return render(request, "core/index.html", context)
@@ -478,17 +488,72 @@ def request_blood(request):
}
return render(request, 'core/request_blood.html', context)
+import csv
+from django.http import HttpResponse
+
@login_required
def donation_history(request):
- """View to display the full history of completed donations."""
+ """View to display the full history of completed donations with filtering and export."""
completed_donations = DonationEvent.objects.filter(is_completed=True).select_related('donor_user', 'request').order_by('-date')
+ # Filtering
+ blood_group = request.GET.get('blood_group')
+ location = request.GET.get('location')
+ export = request.GET.get('export')
+
+ if blood_group:
+ completed_donations = completed_donations.filter(request__blood_group=blood_group)
+ if location:
+ completed_donations = completed_donations.filter(request__location__icontains=location)
+
+ # Export to CSV
+ if export == 'csv':
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = 'attachment; filename="donation_history.csv"'
+
+ writer = csv.writer(response)
+ writer.writerow(['Donor', 'Blood Group', 'Patient', 'Location', 'Hospital', 'Date'])
+
+ for donation in completed_donations:
+ writer.writerow([
+ donation.donor_user.username if donation.donor_user else donation.donor.name,
+ donation.request.blood_group,
+ donation.request.patient_name,
+ donation.request.location,
+ donation.request.hospital,
+ donation.date.strftime('%Y-%m-%d %H:%M')
+ ])
+ return response
+
context = {
'donations': completed_donations,
- 'title': 'Donation History & Lives Saved'
+ 'title': 'Donation History',
+ 'blood_groups': [g[0] for g in BLOOD_GROUPS],
+ 'current_filters': {
+ 'blood_group': blood_group,
+ 'location': location
+ }
}
return render(request, 'core/donation_history.html', context)
+@login_required
+def lives_saved(request):
+ """View to display the impact and lives saved through donations."""
+ completed_donations = DonationEvent.objects.filter(is_completed=True).select_related('donor_user', 'request').order_by('-date')
+
+ total_donations = completed_donations.count()
+ # Demo data from home view to keep consistency
+ demo_donations = 157
+ total_impact = (total_donations + demo_donations) * 3
+
+ context = {
+ 'donations': completed_donations[:10], # Show recent impact
+ 'total_donations': total_donations + demo_donations,
+ 'total_impact': total_impact,
+ 'title': 'Lives Saved & Community Impact',
+ }
+ return render(request, 'core/lives_saved.html', context)
+
@login_required
@login_required
def vaccination_dashboard(request):