Autosave: 20260218-073239

This commit is contained in:
Flatlogic Bot 2026-02-18 07:32:39 +00:00
parent 800e9db512
commit dba5db4b7d
37 changed files with 427 additions and 89 deletions

View File

@ -193,3 +193,6 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = '/login/' LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

View File

@ -28,3 +28,4 @@ urlpatterns = [
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -2,6 +2,18 @@ from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .models import UserProfile, BLOOD_GROUPS from .models import UserProfile, BLOOD_GROUPS
from django.contrib.auth.forms import UserCreationForm
class UserRegisterForm(UserCreationForm):
email = forms.EmailField(required=True)
blood_group = forms.ChoiceField(choices=BLOOD_GROUPS, required=True)
location = forms.CharField(max_length=255, required=True)
phone = forms.CharField(max_length=20, required=True)
class Meta:
model = User
fields = ['username', 'email']
class UserUpdateForm(forms.ModelForm): class UserUpdateForm(forms.ModelForm):
email = forms.EmailField() email = forms.EmailField()
@ -18,10 +30,11 @@ class UserUpdateForm(forms.ModelForm):
class ProfileUpdateForm(forms.ModelForm): class ProfileUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = ['bio', 'location', 'phone', 'blood_group'] fields = ['bio', 'location', 'phone', 'blood_group', 'profile_pic']
widgets = { widgets = {
'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'location': forms.TextInput(attrs={'class': 'form-control'}), 'location': forms.TextInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}), 'phone': forms.TextInput(attrs={'class': 'form-control'}),
'blood_group': forms.Select(attrs={'class': 'form-control'}, choices=BLOOD_GROUPS), 'blood_group': forms.Select(attrs={'class': 'form-control'}, choices=BLOOD_GROUPS),
'profile_pic': forms.FileInput(attrs={'class': 'form-control'}),
} }

View File

Binary file not shown.

View File

View File

@ -0,0 +1,46 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from core.models import BloodRequest
from datetime import timedelta
class Command(BaseCommand):
help = 'Deletes old blood requests based on urgency and status'
def handle(self, *args, **options):
now = timezone.now()
# Define thresholds (days)
thresholds = {
'CRITICAL': 3,
'URGENT': 7,
'NORMAL': 15,
}
deleted_count = 0
# 1. Delete by Urgency
for urgency, days in thresholds.items():
cutoff = now - timedelta(days=days)
old_requests = BloodRequest.objects.filter(
urgency=urgency,
created_at__lt=cutoff
)
count = old_requests.count()
if count > 0:
old_requests.delete()
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
cutoff_inactive = now - timedelta(days=7)
inactive_requests = BloodRequest.objects.exclude(status='Active').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.'))
deleted_count += count_inactive
if deleted_count == 0:
self.stdout.write(self.style.SUCCESS('No old requests to delete.'))
else:
self.stdout.write(self.style.SUCCESS(f'Cleanup complete. Total deleted: {deleted_count}'))

View File

@ -0,0 +1,47 @@
from django.core.management.base import BaseCommand
from core.models import Hospital
class Command(BaseCommand):
help = 'Seed the database with Kathmandu hospitals and coordinates'
def handle(self, *args, **kwargs):
hospitals = [
{"name": "Bir Hospital", "location": "Kanti Path, Kathmandu", "phone": "01-4221988", "lat": 27.7058, "lng": 85.3135},
{"name": "Kathmandu Valley Hospital", "location": "Bagh darbar marg, Kathmandu", "phone": "01-4255330", "lat": 27.7015, "lng": 85.3115},
{"name": "Civil Service Hospital of Nepal", "location": "Minbhawan marg, Kathmandu", "phone": "01-4793000", "website": "https://csh.gov.np/ne", "lat": 27.6841, "lng": 85.3385},
{"name": "Venus Hospital", "location": "Devkota Sadak, Kathmandu", "phone": "01-4475120", "lat": 27.6945, "lng": 85.3415},
{"name": "Vayodha Hospital", "location": "Balkhu, Kathmandu", "phone": "01-4281666", "website": "https://www.vayodhahospitals.com/", "lat": 27.6855, "lng": 85.2935},
{"name": "Grande City Hospital", "location": "Kanti path, Kathmandu", "phone": "01-4163500", "website": "http://grandecityhospital.com/", "lat": 27.7065, "lng": 85.3135},
{"name": "Teaching Hospital", "location": "Maharajgunj, Kathmandu", "phone": "01-4412303", "website": "http://iom.edu.np/", "lat": 27.7351, "lng": 85.3315},
{"name": "Kathmandu Hospital", "location": "Tripureshwor marg, Kathmandu", "phone": "01-4229656", "lat": 27.6935, "lng": 85.3145},
{"name": "Kathmandu Neuro & General Hospital", "location": "Bagh Durbar marg, Kathmandu", "phone": "01-5327735", "lat": 27.7018, "lng": 85.3118},
{"name": "Teku Hospital", "location": "Teku, Kathmandu", "phone": "01-4253396", "website": "http://www.stidh.gov.np/", "lat": 27.6961, "lng": 85.3025},
{"name": "Nepal Eye Hospital", "location": "Tripureswor, Kathmandu", "phone": "01-4260813", "website": "https://www.nepaleyehospital.org/", "lat": 27.6940, "lng": 85.3140},
{"name": "Everest Hospital Pvt. Ltd.", "location": "Kathmandu", "phone": "01-4793024", "website": "http://everesthospital.org.np/", "lat": 27.6925, "lng": 85.3345},
{"name": "Om Hospital & Research Center", "location": "Chabil, Kathmandu", "phone": "01-4476225", "website": "https://omhospitalnepal.com/", "lat": 27.7175, "lng": 85.3485},
{"name": "Annapurna Neuro Hospital", "location": "Maitighar mandala", "phone": "01-4256656", "website": "https://www.annapurnahospitals.com", "lat": 27.6945, "lng": 85.3215},
{"name": "Norvic International Hospital", "location": "Kathmandu", "phone": "01-5970032", "website": "https://www.norvichospital.com/", "lat": 27.6897, "lng": 85.3235},
{"name": "Blue Cross Hospital", "location": "Tripura marga, Kathmandu", "phone": "01-4262027", "lat": 27.6948, "lng": 85.3148},
{"name": "Paropakar Maternity and Womens Hospital", "location": "Thapathali, Kathmandu", "phone": "01-4261363", "lat": 27.6915, "lng": 85.3215},
{"name": "ERA INTERNATIONAL HOSPITAL Pvt. Ltd.", "location": "Kathmandu", "phone": "01-4352447", "website": "https://www.era-hospital.com/", "lat": 27.7215, "lng": 85.3015},
{"name": "Birendra Military Hospital", "location": "Kathmandu", "phone": "01-4271941", "website": "https://birendrahospital.nepalarmy.mil.np/", "lat": 27.7085, "lng": 85.2815},
{"name": "Capital Hospital", "location": "Kathmandu", "phone": "01-4168222", "lat": 27.7045, "lng": 85.3215},
{"name": "Valley Maternity Nursing Home", "location": "Kathmandu", "phone": "01-4420224", "lat": 27.7125, "lng": 85.3245},
{"name": "Medicare National Hospital & Research Center", "location": "Ring road, kathmandu", "phone": "01-4467067", "website": "http://medicarehosp.com/", "lat": 27.7145, "lng": 85.3415},
{"name": "CIWEC Hospital Pvt. Ltd.", "location": "Kathmandu", "phone": "01-4435232", "lat": 27.7165, "lng": 85.3215},
{"name": "Nepal-Bharat Maitri Hospital", "location": "Dakshinha murti marga, Kathmandu", "phone": "01-5241288", "lat": 27.7185, "lng": 85.3515},
{"name": "Kantipur Hospital", "location": "Shriganesh Marg, Tripureshwor", "phone": "01-4111", "lat": 27.6938, "lng": 85.3142},
]
for h_data in hospitals:
Hospital.objects.update_or_create(
name=h_data['name'],
defaults={
'location': h_data['location'],
'phone': h_data['phone'],
'website': h_data.get('website', ''),
'latitude': h_data.get('lat'),
'longitude': h_data.get('lng'),
}
)
self.stdout.write(self.style.SUCCESS(f'Successfully updated {len(hospitals)} hospitals with coordinates.'))

View File

@ -0,0 +1,16 @@
# Generated by Django 5.2.7 on 2026-02-18 05:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0009_bloodrequest_user_donor_user_donationevent_feedback_and_more'),
]
operations = [
migrations.DeleteModel(
name='Feedback',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2026-02-18 06:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_delete_feedback'),
]
operations = [
migrations.CreateModel(
name='Hospital',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('location', models.CharField(max_length=255)),
('phone', models.CharField(blank=True, max_length=100, null=True)),
('website', models.URLField(blank=True, null=True)),
],
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2026-02-18 06:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_hospital'),
]
operations = [
migrations.AddField(
model_name='hospital',
name='latitude',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
migrations.AddField(
model_name='hospital',
name='longitude',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-02-18 07:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0012_hospital_latitude_hospital_longitude'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='profile_pic',
field=models.ImageField(blank=True, null=True, upload_to='profile_pics'),
),
]

View File

@ -18,6 +18,7 @@ class UserProfile(models.Model):
birth_date = models.DateField(null=True, blank=True) birth_date = models.DateField(null=True, blank=True)
phone = models.CharField(max_length=20, blank=True) phone = models.CharField(max_length=20, blank=True)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS, blank=True) blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS, blank=True)
profile_pic = models.ImageField(upload_to='profile_pics', blank=True, null=True)
def __str__(self): def __str__(self):
return self.user.username return self.user.username
@ -32,6 +33,16 @@ def create_or_save_user_profile(sender, instance, created, **kwargs):
UserProfile.objects.create(user=instance) UserProfile.objects.create(user=instance)
instance.profile.save() instance.profile.save()
@receiver(post_save, sender=UserProfile)
def sync_donor_profile(sender, instance, **kwargs):
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.blood_group = instance.blood_group
donor.location = instance.location
donor.phone = instance.phone
donor.save()
class VaccineRecord(models.Model): class VaccineRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
vaccine_name = models.CharField(max_length=100) vaccine_name = models.CharField(max_length=100)
@ -66,6 +77,17 @@ class Donor(models.Model):
def __str__(self): def __str__(self):
return f"{self.name} ({self.blood_group}) - {'Verified' if self.is_verified else 'Unverified'}" return f"{self.name} ({self.blood_group}) - {'Verified' if self.is_verified else 'Unverified'}"
class Hospital(models.Model):
name = models.CharField(max_length=255)
location = models.CharField(max_length=255)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
phone = models.CharField(max_length=100, null=True, blank=True)
website = models.URLField(null=True, blank=True)
def __str__(self):
return self.name
class BloodRequest(models.Model): class BloodRequest(models.Model):
URGENCY_LEVELS = [ URGENCY_LEVELS = [
('CRITICAL', 'Critical'), ('CRITICAL', 'Critical'),
@ -126,12 +148,3 @@ class Notification(models.Model):
def __str__(self): def __str__(self):
return f"Notification for {self.user.username}: {self.message[:20]}..." return f"Notification for {self.user.username}: {self.message[:20]}..."
class Feedback(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='feedbacks')
content = models.TextField()
rating = models.PositiveIntegerField(default=5)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Feedback from {self.user.username} - {self.rating} stars"

View File

@ -218,6 +218,10 @@
z-index: -1; z-index: -1;
} }
.dropdown-toggle.no-caret::after {
display: none;
}
.sos-content { .sos-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -272,11 +276,10 @@
<li class="{% if request.resolver_match.url_name == 'donor_list' %}active{% endif %}"><a href="{% url 'donor_list' %}"><i class="bi bi-people-fill"></i> {% trans "Donors" %}</a></li> <li class="{% if request.resolver_match.url_name == 'donor_list' %}active{% endif %}"><a href="{% url 'donor_list' %}"><i class="bi bi-people-fill"></i> {% trans "Donors" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'blood_request_list' %}active{% endif %}"><a href="{% url 'blood_request_list' %}"><i class="bi bi-megaphone-fill"></i> {% trans "Blood Requests" %}</a></li> <li class="{% if request.resolver_match.url_name == 'blood_request_list' %}active{% endif %}"><a href="{% url 'blood_request_list' %}"><i class="bi bi-megaphone-fill"></i> {% trans "Blood Requests" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'blood_bank_list' %}active{% endif %}"><a href="{% url 'blood_bank_list' %}"><i class="bi bi-hospital-fill"></i> {% trans "Blood Banks" %}</a></li> <li class="{% if request.resolver_match.url_name == 'blood_bank_list' %}active{% endif %}"><a href="{% url 'blood_bank_list' %}"><i class="bi bi-hospital-fill"></i> {% trans "Blood Banks" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'hospital_list' %}active{% endif %}"><a href="{% url 'hospital_list' %}"><i class="bi bi-building-heart"></i> {% trans "Hospitals" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'live_map' %}active{% endif %}"><a href="{% url 'live_map' %}"><i class="bi bi-map text-danger"></i> {% trans "Live Alerts" %}</a></li> <li class="{% if request.resolver_match.url_name == 'live_map' %}active{% endif %}"><a href="{% url 'live_map' %}"><i class="bi bi-map text-danger"></i> {% trans "Live Alerts" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'vaccination_info' %}active{% endif %}"><a href="{% url 'vaccination_info' %}"><i class="bi bi-shield-check"></i> {% trans "Vaccination" %}</a></li> <li class="{% if request.resolver_match.url_name == 'vaccination_info' %}active{% endif %}"><a href="{% url 'vaccination_info' %}"><i class="bi bi-shield-check"></i> {% trans "Vaccination" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'submit_feedback' %}active{% endif %}"><a href="{% url 'submit_feedback' %}"><i class="bi bi-chat-left-heart-fill"></i> {% trans "Feedback" %}</a></li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="{% if request.resolver_match.url_name == 'profile' %}active{% endif %}"><a href="{% url 'profile' %}"><i class="bi bi-person-bounding-box"></i> {% trans "My Profile" %}</a></li>
<li class="{% if 'vaccination_dashboard' in request.resolver_match.url_name or 'add_vaccination' in request.resolver_match.url_name %}active{% endif %}"><a href="{% url 'vaccination_dashboard' %}"><i class="bi bi-journal-check"></i> {% trans "My Records" %}</a></li> <li class="{% if 'vaccination_dashboard' in request.resolver_match.url_name or 'add_vaccination' in request.resolver_match.url_name %}active{% endif %}"><a href="{% url 'vaccination_dashboard' %}"><i class="bi bi-journal-check"></i> {% trans "My Records" %}</a></li>
{% endif %} {% endif %}
<li><a href="/admin/"><i class="bi bi-gear-fill"></i> {% trans "Settings" %}</a></li> <li><a href="/admin/"><i class="bi bi-gear-fill"></i> {% trans "Settings" %}</a></li>
@ -340,13 +343,43 @@
{{ user.notifications.count }} {{ user.notifications.count }}
</span> </span>
</a> </a>
<a href="{% url 'profile' %}" class="text-decoration-none d-flex align-items-center gap-2">
<div class="bg-danger bg-opacity-10 rounded-circle p-1 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;"> <!-- Profile Dropdown -->
<i class="bi bi-person-fill text-danger"></i> <div class="dropdown">
</div> <a href="#" class="text-decoration-none d-flex align-items-center gap-2 dropdown-toggle no-caret" id="profileDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="d-none d-sm-inline fw-bold text-dark">{{ user.username }}</span> <div class="rounded-circle overflow-hidden border border-danger-subtle d-flex align-items-center justify-content-center" style="width: 35px; height: 35px;">
</a> {% if user.profile.profile_pic %}
<a href="{% url 'logout' %}" class="btn btn-outline-danger btn-sm">Logout</a> <img src="{{ user.profile.profile_pic.url }}" alt="{{ user.username }}" class="w-100 h-100 object-fit-cover">
{% else %}
<div class="bg-danger bg-opacity-10 w-100 h-100 d-flex align-items-center justify-content-center">
<i class="bi bi-person-fill text-danger"></i>
</div>
{% endif %}
</div>
<span class="d-none d-sm-inline fw-bold text-dark">{{ user.username }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2" aria-labelledby="profileDropdown">
<li>
<a class="dropdown-item d-flex align-items-center gap-2 py-2" href="{% url 'profile' %}">
<i class="bi bi-person-circle text-danger"></i>
<span>{% trans "My Profile" %}</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 py-2" href="{% url 'profile' %}">
<i class="bi bi-pencil-square text-danger"></i>
<span>{% trans "Change Name" %}</span>
</a>
</li>
<li><hr class="dropdown-divider opacity-50"></li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 py-2 text-danger" href="{% url 'logout' %}">
<i class="bi bi-box-arrow-right"></i>
<span>{% trans "Logout" %}</span>
</a>
</li>
</ul>
</div>
</div> </div>
{% else %} {% else %}
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">

View File

@ -45,8 +45,17 @@
{% for donor in donors %} {% for donor in donors %}
<div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3 p-3 mb-3 border rounded bg-light"> <div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3 p-3 mb-3 border rounded bg-light">
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<div class="bg-danger bg-opacity-10 text-danger rounded p-2 fw-bold" style="width: 45px; height: 45px; display: flex; align-items: center; justify-content: center;"> <div class="position-relative">
{{ donor.blood_group }} {% if donor.user and donor.user.profile.profile_pic %}
<img src="{{ donor.user.profile.profile_pic.url }}" alt="{{ donor.name }}" class="rounded-circle" style="width: 50px; height: 50px; object-fit: cover;">
<span class="position-absolute bottom-0 end-0 badge rounded-pill bg-danger border border-white" style="font-size: 0.65rem; padding: 0.25rem 0.45rem;">
{{ donor.blood_group }}
</span>
{% else %}
<div class="bg-danger bg-opacity-10 text-danger rounded p-2 fw-bold" style="width: 45px; height: 45px; display: flex; align-items: center; justify-content: center;">
{{ donor.blood_group }}
</div>
{% endif %}
</div> </div>
<div> <div>
<h6 class="mb-0 fw-bold text-dark"> <h6 class="mb-0 fw-bold text-dark">

View File

@ -1,34 +0,0 @@
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="glass-card p-4">
<h2 class="mb-4">{% trans "Share Your Feedback" %}</h2>
<p class="text-muted">{% trans "We value your experience. Please let us know how we can improve." %}</p>
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label for="rating" class="form-label">{% trans "Rating" %}</label>
<select name="rating" id="rating" class="form-select">
<option value="5">⭐⭐⭐⭐⭐ (Excellent)</option>
<option value="4">⭐⭐⭐⭐ (Good)</option>
<option value="3">⭐⭐⭐ (Average)</option>
<option value="2">⭐⭐ (Poor)</option>
<option value="1">⭐ (Very Poor)</option>
</select>
</div>
<div class="mb-3">
<label for="content" class="form-label">{% trans "Your Feedback" %}</label>
<textarea name="content" id="content" rows="5" class="form-control" placeholder="{% trans 'Tell us about your experience...' %}" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">{% trans "Submit Feedback" %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,92 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Hospitals - RaktaPulse{% endblock %}
{% block content %}
<div class="container-fluid p-0">
<div class="row mb-4">
<div class="col-md-8">
<h2 class="brand-font mb-1">Hospitals in Kathmandu</h2>
<p class="text-secondary">A comprehensive list of public and private hospitals for blood requests and emergency care.</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<button id="nearMeBtn" class="btn btn-glass btn-sm rounded-pill">
<i class="bi bi-geo-fill me-1"></i> Nearest First
</button>
</div>
</div>
<div class="row">
{% for hospital in hospitals %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="glass-card h-100 d-flex flex-column">
<div class="mb-3">
<div class="d-flex justify-content-between align-items-start">
<h4 class="brand-font mb-1 text-dark">{{ hospital.name }}</h4>
{% if hospital.distance %}
<span class="badge bg-danger-soft text-danger rounded-pill small">
{{ hospital.distance|floatformat:1 }} km away
</span>
{% endif %}
</div>
<p class="text-secondary small mb-0"><i class="bi bi-geo-alt me-1"></i> {{ hospital.location }}</p>
</div>
<div class="mt-auto">
{% if hospital.phone %}
<p class="mb-2">
<a href="tel:{{ hospital.phone }}" class="text-decoration-none text-danger fw-bold">
<i class="bi bi-telephone me-2"></i> {{ hospital.phone }}
</a>
</p>
{% endif %}
{% if hospital.website %}
<p class="mb-3">
<a href="{{ hospital.website }}" target="_blank" class="text-decoration-none text-primary small">
<i class="bi bi-globe me-2"></i> Visit Website
</a>
</p>
{% endif %}
<a href="{% url 'request_blood' %}?hospital={{ hospital.name|urlencode }}" class="btn btn-outline-danger btn-sm w-100 rounded-pill">Request Blood Here</a>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<i class="bi bi-hospital fs-1 text-secondary opacity-25"></i>
<p class="text-secondary mt-3">No hospitals registered in the system.</p>
</div>
{% endfor %}
</div>
</div>
<script>
document.getElementById('nearMeBtn').addEventListener('click', function() {
if (navigator.geolocation) {
this.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span> Locating...';
navigator.geolocation.getCurrentPosition(function(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
window.location.href = `?lat=${lat}&lng=${lng}`;
}, function(error) {
console.error("Error getting location: ", error);
alert("Could not get your location. Please ensure location services are enabled.");
document.getElementById('nearMeBtn').innerHTML = '<i class="bi bi-geo-fill me-1"></i> Nearest First';
});
} else {
alert("Geolocation is not supported by this browser.");
}
});
// Automatically check if we should show distance
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('lat') && urlParams.has('lng')) {
document.getElementById('nearMeBtn').classList.add('btn-danger');
document.getElementById('nearMeBtn').classList.remove('btn-glass');
document.getElementById('nearMeBtn').innerHTML = '<i class="bi bi-check-circle-fill me-1"></i> Sorted by Proximity';
}
</script>
{% endblock %}

View File

@ -224,8 +224,18 @@
{% for donor in donors %} {% for donor in donors %}
<div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3"> <div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3">
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<div class="blood-group-pill">{{ donor.blood_group }}</div> <div class="position-relative">
{% if donor.user and donor.user.profile.profile_pic %}
<img src="{{ donor.user.profile.profile_pic.url }}" alt="{{ donor.name }}" class="rounded-circle" style="width: 45px; height: 45px; object-fit: cover;">
<span class="position-absolute bottom-0 end-0 badge rounded-pill bg-danger border border-white" style="font-size: 0.6rem; padding: 0.2rem 0.4rem;">
{{ donor.blood_group }}
</span>
{% else %}
<div class="blood-group-pill">{{ donor.blood_group }}</div>
{% endif %}
</div>
<div> <div>
<h6 class="mb-0 fw-bold text-dark"> <h6 class="mb-0 fw-bold text-dark">
{{ donor.name }} {{ donor.name }}
{% if donor.is_verified %} {% if donor.is_verified %}

View File

@ -1,4 +1,4 @@
{% extends 'core/base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}

View File

@ -9,16 +9,20 @@
<div class="col-lg-8"> <div class="col-lg-8">
<div class="glass-card"> <div class="glass-card">
<div class="d-flex align-items-center mb-4"> <div class="d-flex align-items-center mb-4">
<div class="bg-danger rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;"> {% if user.profile.profile_pic %}
<i class="bi bi-person-fill text-white fs-2"></i> <img src="{{ user.profile.profile_pic.url }}" alt="{{ user.username }}" class="rounded-circle me-3 object-fit-cover" style="width: 64px; height: 64px;">
</div> {% else %}
<div class="bg-danger rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
<i class="bi bi-person-fill text-white fs-2"></i>
</div>
{% endif %}
<div> <div>
<h2 class="mb-0 fw-bold">{{ user.username }}</h2> <h2 class="mb-0 fw-bold">{{ user.username }}</h2>
<p class="text-secondary mb-0">Member since {{ user.date_joined|date:"M d, Y" }}</p> <p class="text-secondary mb-0">Member since {{ user.date_joined|date:"M d, Y" }}</p>
</div> </div>
</div> </div>
<form method="POST"> <form method="POST" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<h5 class="fw-bold mb-3 border-bottom pb-2">Account Information</h5> <h5 class="fw-bold mb-3 border-bottom pb-2">Account Information</h5>
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
@ -59,6 +63,10 @@
<label class="form-label small fw-bold">Location</label> <label class="form-label small fw-bold">Location</label>
{{ p_form.location }} {{ p_form.location }}
</div> </div>
<div class="col-12">
<label class="form-label small fw-bold">Profile Picture</label>
{{ p_form.profile_pic }}
</div>
</div> </div>
<div class="d-grid"> <div class="d-grid">

View File

@ -1,4 +1,4 @@
{% extends 'core/base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}

View File

@ -34,7 +34,7 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-bold text-secondary">Hospital Name *</label> <label class="form-label small fw-bold text-secondary">Hospital Name *</label>
<input type="text" name="hospital" class="form-control rounded-3" required> <input type="text" name="hospital" class="form-control rounded-3" value="{{ selected_hospital }}" required>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-bold text-secondary">Urgency *</label> <label class="form-label small fw-bold text-secondary">Urgency *</label>

View File

@ -5,8 +5,8 @@ from .views import (
blood_request_list, blood_bank_list, vaccination_info, blood_request_list, blood_bank_list, vaccination_info,
vaccination_dashboard, add_vaccination, live_map, vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request, request_blood, profile, volunteer_for_request,
complete_donation, submit_feedback, notifications_view, complete_donation, notifications_view,
register_donor register_donor, hospital_list
) )
urlpatterns = [ urlpatterns = [
@ -25,7 +25,7 @@ urlpatterns = [
path("request-blood/", request_blood, name="request_blood"), path("request-blood/", request_blood, name="request_blood"),
path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"), path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"),
path("complete-donation/<int:event_id>/", complete_donation, name="complete_donation"), path("complete-donation/<int:event_id>/", complete_donation, name="complete_donation"),
path("feedback/", submit_feedback, name="submit_feedback"),
path("notifications/", notifications_view, name="notifications"), path("notifications/", notifications_view, name="notifications"),
path("register-donor/", register_donor, name="register_donor"), path("register-donor/", register_donor, name="register_donor"),
path("hospitals/", hospital_list, name="hospital_list"),
] ]

View File

@ -1,5 +1,6 @@
import os import os
import platform import platform
import math
from django.db import models from django.db import models
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate from django.contrib.auth import login, logout, authenticate
@ -7,9 +8,32 @@ from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Feedback from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Hospital
from .forms import UserUpdateForm, ProfileUpdateForm from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm
import math
def hospital_list(request):
user_lat = request.GET.get('lat')
user_lng = request.GET.get('lng')
hospitals = Hospital.objects.all()
hospital_list_data = list(hospitals)
if user_lat and user_lng:
try:
u_lat = float(user_lat)
u_lng = float(user_lng)
for h in hospital_list_data:
if h.latitude and h.longitude:
h.distance = haversine(u_lat, u_lng, float(h.latitude), float(h.longitude))
else:
h.distance = 999999
hospital_list_data.sort(key=lambda x: x.distance)
except ValueError:
hospital_list_data.sort(key=lambda x: x.name)
else:
hospital_list_data.sort(key=lambda x: x.name)
return render(request, 'core/hospital_list.html', {'hospitals': hospital_list_data})
@login_required @login_required
def profile(request): def profile(request):
@ -18,7 +42,7 @@ def profile(request):
if request.method == 'POST': if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user) u_form = UserUpdateForm(request.POST, instance=request.user)
p_form = ProfileUpdateForm(request.POST, instance=profile) p_form = ProfileUpdateForm(request.POST, request.FILES, instance=profile)
if u_form.is_valid() and p_form.is_valid(): if u_form.is_valid() and p_form.is_valid():
u_form.save() u_form.save()
p_form.save() p_form.save()
@ -67,13 +91,20 @@ def logout_view(request):
def register_view(request): def register_view(request):
if request.method == "POST": if request.method == "POST":
form = UserCreationForm(request.POST) form = UserRegisterForm(request.POST)
if form.is_valid(): if form.is_valid():
user = form.save() user = form.save()
profile = user.profile
profile.blood_group = form.cleaned_data.get('blood_group')
profile.location = form.cleaned_data.get('location')
profile.phone = form.cleaned_data.get('phone')
profile.save()
login(request, user) login(request, user)
messages.success(request, f"Welcome to RaktaPulse, {user.username}! You are now a registered donor.")
return redirect("home") return redirect("home")
else: else:
form = UserCreationForm() form = UserRegisterForm()
return render(request, "core/register.html", {"form": form}) return render(request, "core/register.html", {"form": form})
def home(request): def home(request):
@ -276,6 +307,7 @@ def request_blood(request):
context = { context = {
'blood_groups': [g[0] for g in BLOOD_GROUPS], 'blood_groups': [g[0] for g in BLOOD_GROUPS],
'urgency_levels': BloodRequest.URGENCY_LEVELS, 'urgency_levels': BloodRequest.URGENCY_LEVELS,
'selected_hospital': request.GET.get('hospital', ''),
} }
return render(request, 'core/request_blood.html', context) return render(request, 'core/request_blood.html', context)
@ -355,12 +387,12 @@ def complete_donation(request, event_id):
# Notify both # Notify both
Notification.objects.create( Notification.objects.create(
user=event.donor_user, user=event.donor_user,
message=f"Thank you for your donation to {event.request.patient_name}! Your feedback is valuable to us." message=f"Thank you for your donation to {event.request.patient_name}!"
) )
if event.request.user: if event.request.user:
Notification.objects.create( Notification.objects.create(
user=event.request.user, user=event.request.user,
message=f"We hope the donation for {event.request.patient_name} went well. Please share your feedback!" message=f"We hope the donation for {event.request.patient_name} went well."
) )
messages.success(request, "Donation marked as completed. Thank you!") messages.success(request, "Donation marked as completed. Thank you!")
@ -369,21 +401,6 @@ def complete_donation(request, event_id):
return redirect('home') return redirect('home')
@login_required
def submit_feedback(request):
if request.method == "POST":
content = request.POST.get('content')
rating = request.POST.get('rating')
if content:
Feedback.objects.create(
user=request.user,
content=content,
rating=rating if rating else 5
)
messages.success(request, "Thank you for your feedback!")
return redirect('home')
return render(request, 'core/feedback.html')
@login_required @login_required
def notifications_view(request): def notifications_view(request):
notifications = Notification.objects.filter(user=request.user).order_by('-created_at') notifications = Notification.objects.filter(user=request.user).order_by('-created_at')