Autosave: 20260227-133224

This commit is contained in:
Flatlogic Bot 2026-02-27 13:32:25 +00:00
parent 24205f808f
commit 34ef272e25
9 changed files with 199 additions and 7 deletions

View File

@ -1,5 +1,6 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from django.db.models import Q
from core.models import BloodRequest
from datetime import timedelta
@ -31,15 +32,29 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS(f'Deleted {count} {urgency} requests older than {days} days.'))
deleted_count += count
# 2. Delete non-Active requests older than 7 days
# 2. Delete non-Active and non-Accepted requests older than 7 days
cutoff_inactive = now - timedelta(days=7)
inactive_requests = BloodRequest.objects.exclude(status='Active').filter(created_at__lt=cutoff_inactive)
inactive_requests = BloodRequest.objects.exclude(
Q(status='Active') | Q(status='Accepted')
).filter(created_at__lt=cutoff_inactive)
count_inactive = inactive_requests.count()
if count_inactive > 0:
inactive_requests.delete()
self.stdout.write(self.style.SUCCESS(f'Deleted {count_inactive} non-active requests older than 7 days.'))
self.stdout.write(self.style.SUCCESS(f'Deleted {count_inactive} non-active/non-accepted requests older than 7 days.'))
deleted_count += count_inactive
# 3. For Accepted requests, we keep them for 30 days after acceptance for history
cutoff_accepted = now - timedelta(days=30)
old_accepted = BloodRequest.objects.filter(
status='Accepted',
accepted_at__lt=cutoff_accepted
)
count_accepted = old_accepted.count()
if count_accepted > 0:
old_accepted.delete()
self.stdout.write(self.style.SUCCESS(f'Deleted {count_accepted} accepted requests older than 30 days.'))
deleted_count += count_accepted
if deleted_count == 0:
self.stdout.write(self.style.SUCCESS('No old requests to delete.'))
else:

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-02-27 13:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0020_vaccinerecord_photo'),
]
operations = [
migrations.AddField(
model_name='bloodrequest',
name='accepted_at',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -137,6 +137,7 @@ class BloodRequest(models.Model):
image = models.ImageField(upload_to='blood_requests/', null=True, blank=True)
required_date = models.DateField(default=timezone.now)
status = models.CharField(max_length=20, default='Active')
accepted_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):

View File

@ -80,6 +80,94 @@
</div>
</form>
<h5 class="fw-bold mb-3 border-bottom pb-2">
<i class="bi bi-clock-history me-2 text-danger"></i>Transaction & Operation History
</h5>
<div class="mb-5">
<h6 class="fw-bold text-muted small mb-3">Donor Record (People You've Helped)</h6>
<div class="stats-mini bg-light p-3 rounded mb-3">
<div class="row text-center">
<div class="col-6 border-end">
<div class="h4 mb-0 fw-bold text-danger">{{ donations_made.count }}</div>
<div class="small text-muted">Total Volunteers</div>
</div>
<div class="col-6">
<div class="h4 mb-0 fw-bold text-success">{{ completed_donations_count }}</div>
<div class="small text-muted">Successful Donations</div>
</div>
</div>
</div>
{% if donations_made %}
<div class="list-group list-group-flush border rounded overflow-hidden">
{% for donation in donations_made %}
<div class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="fw-bold">Helped {{ donation.request.patient_name }}</span>
<div class="small text-muted">{{ donation.request.hospital }} | {{ donation.date|date:"M d, Y" }}</div>
</div>
<div>
{% if donation.is_completed %}
<span class="badge bg-success">Completed</span>
{% else %}
<span class="badge bg-warning text-dark">Pending</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center p-4 bg-light rounded border border-dashed">
<p class="text-muted small mb-0">You haven't volunteered for any requests yet.</p>
</div>
{% endif %}
</div>
<div class="mb-5">
<h6 class="fw-bold text-muted small mb-3">Request History (Who Helped You)</h6>
{% if requests_made %}
<div class="list-group list-group-flush border rounded overflow-hidden">
{% for br in requests_made %}
<div class="list-group-item">
<div class="d-flex justify-content-between align-items-start">
<div>
<span class="fw-bold">{{ br.blood_group }} for {{ br.patient_name }}</span>
<div class="small text-muted">{{ br.created_at|date:"M d, Y" }} | Status: <strong>{{ br.status }}</strong></div>
<div class="mt-2">
<p class="small fw-bold mb-1">Volunteers:</p>
{% with br.donations.all as donations %}
{% if donations %}
<div class="d-flex flex-wrap gap-2">
{% for d in donations %}
<a href="{% url 'public_profile' d.donor_user.username %}" class="btn btn-sm btn-outline-danger py-0 px-2 rounded-pill small">
<i class="bi bi-person-heart me-1"></i>{{ d.donor_user.username }}
</a>
{% endfor %}
</div>
{% else %}
<span class="text-muted small italic">No volunteers yet</span>
{% endif %}
{% endwith %}
</div>
</div>
<span class="badge {% if br.status == 'Active' %}bg-danger{% elif br.status == 'Accepted' %}bg-info{% else %}bg-secondary{% endif %}">
{{ br.status }}
</span>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center p-4 bg-light rounded border border-dashed">
<p class="text-muted small mb-0">You haven't made any blood requests yet.</p>
</div>
{% endif %}
</div>
<div class="mt-5 border-top pt-4">
<h5 class="text-danger fw-bold mb-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i>Danger Zone

View File

@ -55,6 +55,47 @@
</div>
</div>
<div class="mb-4">
<h5 class="fw-bold mb-3">Impact & Reliability</h5>
<div class="row g-3 text-center">
<div class="col-6">
<div class="p-3 border rounded-3 bg-light">
<div class="h2 fw-bold text-danger mb-0">{{ donations_made.count }}</div>
<div class="small text-muted text-uppercase fw-bold">Volunteered</div>
</div>
</div>
<div class="col-6">
<div class="p-3 border rounded-3 bg-light">
<div class="h2 fw-bold text-success mb-0">{{ completed_donations_count }}</div>
<div class="small text-muted text-uppercase fw-bold">Successful</div>
</div>
</div>
</div>
</div>
{% if donations_made %}
<div class="mb-4">
<h5 class="fw-bold mb-3">Recent Donations</h5>
<div class="list-group list-group-flush border rounded-3 overflow-hidden">
{% for donation in donations_made|slice:":5" %}
<div class="list-group-item bg-light-subtle">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fw-bold">Donated to {{ donation.request.patient_name }}</div>
<div class="small text-muted">{{ donation.request.hospital }} | {{ donation.date|date:"M d, Y" }}</div>
</div>
{% if donation.is_completed %}
<span class="badge bg-success-subtle text-success border border-success-subtle">Verified</span>
{% else %}
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning-subtle">Pending</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="d-grid gap-2">
{% if user.is_authenticated and user != profile_user %}
<a href="{% url 'chat' profile_user.username %}" class="btn btn-danger py-2">

View File

@ -140,9 +140,16 @@ def profile(request):
u_form = UserUpdateForm(instance=request.user)
p_form = ProfileUpdateForm(instance=profile)
# Fetch History
requests_made = BloodRequest.objects.filter(user=request.user).order_by('-created_at')
donations_made = DonationEvent.objects.filter(donor_user=request.user).select_related('request').order_by('-date')
context = {
'u_form': u_form,
'p_form': p_form
'p_form': p_form,
'requests_made': requests_made,
'donations_made': donations_made,
'completed_donations_count': donations_made.filter(is_completed=True).count()
}
return render(request, 'core/profile.html', context)
@ -263,7 +270,12 @@ def home(request):
else:
donor_list_data.sort(key=lambda x: (-x.is_available, x.name))
blood_requests = BloodRequest.objects.filter(status='Active').order_by('-urgency', '-created_at')
three_days_ago = timezone.now() - timezone.timedelta(days=3)
blood_requests = BloodRequest.objects.filter(
Q(status='Active') |
Q(status='Accepted', accepted_at__gte=three_days_ago)
).order_by('-urgency', '-created_at')
blood_banks = BloodBank.objects.all()
# Stats for Dashboard
@ -275,7 +287,9 @@ def home(request):
stats = {
"total_donors": Donor.objects.count() + demo_donors,
"active_requests": BloodRequest.objects.filter(status='Active').count(),
"active_requests": BloodRequest.objects.filter(
Q(status='Active') | Q(status='Accepted', accepted_at__gte=three_days_ago)
).count(),
"total_stock": sum([
bb.stock_a_plus + bb.stock_a_minus + bb.stock_b_plus + bb.stock_b_minus +
bb.stock_o_plus + bb.stock_o_minus + bb.stock_ab_plus + bb.stock_ab_minus
@ -412,7 +426,11 @@ def vaccination_info(request):
def live_map(request):
"""View to display live alerts/requests on a map."""
active_requests = BloodRequest.objects.filter(status='Active').order_by('-created_at')
three_days_ago = timezone.now() - timezone.timedelta(days=3)
active_requests = BloodRequest.objects.filter(
Q(status='Active') |
Q(status='Accepted', accepted_at__gte=three_days_ago)
).order_by('-created_at')
# Also include blood banks and donors optionally if we want a full map
# But focusing on alerts as requested.
@ -524,6 +542,11 @@ def volunteer_for_request(request, request_id):
request=blood_request,
donor_user=request.user
)
# Update request status and timestamp
blood_request.status = 'Accepted'
blood_request.accepted_at = timezone.now()
blood_request.save()
messages.success(request, "Thank you for volunteering! The requester has been notified.")
# Notify the requester
@ -618,10 +641,16 @@ def public_profile(request, username):
profile = user.profile
donor_profile = getattr(user, 'donor_profile', None)
# Fetch History
donations_made = DonationEvent.objects.filter(donor_user=user).select_related('request').order_by('-date')
completed_donations_count = donations_made.filter(is_completed=True).count()
context = {
'profile_user': user,
'profile': profile,
'donor': donor_profile,
'donations_made': donations_made,
'completed_donations_count': completed_donations_count
}
return render(request, 'core/public_profile.html', context)