From 03ebc3a83e0a05eadb5ea74c7ee890acfa3da68f Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 27 Feb 2026 15:15:37 +0000 Subject: [PATCH] Autosave: 20260227-151537 --- config/__pycache__/urls.cpython-311.pyc | Bin 1759 -> 1984 bytes config/urls.py | 4 + core/__pycache__/admin.cpython-311.pyc | Bin 1937 -> 6209 bytes core/__pycache__/urls.cpython-311.pyc | Bin 2905 -> 2996 bytes core/__pycache__/views.cpython-311.pyc | Bin 43218 -> 46553 bytes core/admin.py | 76 +++++++- core/templates/base.html | 6 +- core/templates/core/donation_history.html | 40 +++- core/templates/core/index.html | 214 ++++++++++++++++------ core/templates/core/lives_saved.html | 91 +++++++++ core/urls.py | 1 + core/views.py | 71 ++++++- 12 files changed, 434 insertions(+), 69 deletions(-) create mode 100644 core/templates/core/lives_saved.html diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 3c20f116c23681f3a0d6c80e060bc66b83583d6b..d1a5bd9b09ff8eb5430c5fdd6466f90fc4125eae 100644 GIT binary patch delta 425 zcmcc5dw`#BIWI340}wpgyf8DAbt0bxTNCetwFAQ(|7Wf^TA8 zVtQ(BYF>##aAk2xYHk&eV@hsjom3nw;PI7*3szOP=0(NZ>g_CPoR2ju4 zFJh@zL!XS1-LJh(ay&>rY6??$UJvo`Jf<+dn9stWXUyJ|% delta 185 zcmX@Wf1j6cIWI340}vceotn9cWg?#hW7d5V3eJ#$SBGLVVVlU6aWXR-@>1&b6=1OVN6B2xeW 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 7b9a1bb8c57ab401e03d74507b7c8a2680cde261..42ebf602a3e0e0971645931ccfc09212f06c2018 100644 GIT binary patch literal 6209 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 NtoT->@CJTL{R@9US33Xz literal 1937 zcmbW1Id2q66vwOj7?0;L*f3u3uFWmn0Un$XtyaRUc7p{6>_X5v;96dasVZ>-7qO=bu+^ z!uK{I|6*bAl)h#T6(!^?5k%04#MGyh2us+J72CcYJH8XUz8jbP5+xRq^y$O^AWPfO za~D$ovB>X4IIoFt!Kyr{-G*^@A@!ea`0Wk~#Gf1?+GB_%AWoc`SQ#NYV~AxSPM(@r z9U;17h!r4KPfe_e+MmR$f5JF;RJ&ZnVe$#%=sDW`HBC~bYb#Njiih%9A+uahn$ikS zHgx?l4}vg}4`q-l5e#DqnZZgN&OzZVk;JEh_?Do)EiB&=w(klDMt1egG00sUA8KpQ z`)HQ%SZb%pq|#N9W|AdE+=4PgV0psy4DV4`dSpC7UM5OYKdCC&GvWnQR`U zGkiau`>S(*f97`QkNxv2od@`;iv#=bjCI8TkV$UV*UVl|6lzT-!KN-{InRqsR{|wr0|~}+*eJ%s>p1rtAmH*f`04U`nzh56 zN2_Cfy^8iIXCa;N^%44d2YtPL)K|10@9PY-j2;v?Ahw5xlmAe zSu4DG6<(~mtmGP;5{ NrUq^hEYb$52LLSeFpmHL delta 111 zcmdlYep5_iIWI340}%8qUYMD{!NBks#DM{MDC6@x=8YOJn7PuWqvTVpgBdh!HruiI vGc)^XDokF@etL2>hcA~7P%R@67a!hyiDMU&;0Iqub$+G>ZV)We0!jb?Rcsj} diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 7b85a88ed00941c12731768e30a26583e9c1803a..50e031b61e378dd748992069c183c080ed640d67 100644 GIT binary patch delta 5400 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 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 @@
    @@ -231,7 +292,7 @@
    -

    Available Donors

    +

    Hero Community

    @@ -239,6 +300,35 @@
    + {% if recent_heroes %} +
    +
    + + + Live: Today's Heroes (Last 24h) + + {{ recent_heroes.count }} Active +
    +
    + {% for hero in recent_heroes %} +
    + {% if hero.donor.user and hero.donor.user.profile.profile_pic %} + + {% else %} +
    + {{ hero.donor.blood_group }} +
    + {% endif %} +
    +

    {{ hero.donor.name }}

    +

    Saved Life {{ hero.date|timesince }} ago

    +
    +
    + {% endfor %} +
    +
    + {% endif %} + @@ -263,59 +353,61 @@ -
    - {% for donor in donors %} -
    -
    -
    - {% if donor.user and donor.user.profile.profile_pic %} - {{ donor.name }} - - {{ donor.blood_group }} - - {% else %} -
    {{ donor.blood_group }}
    - {% endif %} +
    +
    + {% for donor in donors %} +
    +
    +
    + {% if donor.user and donor.user.profile.profile_pic %} + {{ donor.name }} + + {{ 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 %} +

    Last Donated: {{ donor.last_donation_date|default:"Never" }}

    +
    +
    + {% if donor.user %} + + + {% endif %} -
    -

    {{ donor.location }}, {{ donor.district }}

    + Call +
    -
    -
    - - {% if donor.is_available %}Available{% else %}Unavailable{% endif %} - -

    Last Donated: {{ donor.last_donation_date|default:"Never" }}

    -
    -
    - {% 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
    - A+ : {{ bank.stock_a_plus }} - B+ : {{ bank.stock_b_plus }} - O+ : {{ bank.stock_o_plus }} - AB+ : {{ bank.stock_ab_plus }} + A+: {{ bank.stock_a_plus }} + A-: {{ bank.stock_a_minus }} + B+: {{ bank.stock_b_plus }} + B-: {{ bank.stock_b_minus }} + O+: {{ bank.stock_o_plus }} + O-: {{ bank.stock_o_minus }} + AB+: {{ bank.stock_ab_plus }} + AB-: {{ bank.stock_ab_minus }}
    {% 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

    +
    +
    +
    +
    + +
    +
    +

    Recent Life-Saving Events

    +
    +
    +
    + + + + + + + + + + + {% for donation in donations %} + + + + + + + {% empty %} + + + + {% endfor %} + +
    HeroImpactLocationDate
    +
    {{ donation.donor_user.username }}
    +
    Verified Donor
    +
    + + 3 Lives Saved + + +
    {{ donation.request.location }}
    +
    +
    {{ donation.date|date:"M d, Y" }}
    +
    +

    No recent events to display.

    +
    +
    +
    +
    + + +
    +{% 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):