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_REDIRECT_URL = '/'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

View File

@ -28,3 +28,4 @@ urlpatterns = [
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
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 .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):
email = forms.EmailField()
@ -18,10 +30,11 @@ class UserUpdateForm(forms.ModelForm):
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['bio', 'location', 'phone', 'blood_group']
fields = ['bio', 'location', 'phone', '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'}),
'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)
phone = models.CharField(max_length=20, 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):
return self.user.username
@ -32,6 +33,16 @@ def create_or_save_user_profile(sender, instance, created, **kwargs):
UserProfile.objects.create(user=instance)
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):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
vaccine_name = models.CharField(max_length=100)
@ -66,6 +77,17 @@ class Donor(models.Model):
def __str__(self):
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):
URGENCY_LEVELS = [
('CRITICAL', 'Critical'),
@ -126,12 +148,3 @@ class Notification(models.Model):
def __str__(self):
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;
}
.dropdown-toggle.no-caret::after {
display: none;
}
.sos-content {
display: flex;
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 == '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 == '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 == '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 %}
<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>
{% endif %}
<li><a href="/admin/"><i class="bi bi-gear-fill"></i> {% trans "Settings" %}</a></li>
@ -340,13 +343,43 @@
{{ user.notifications.count }}
</span>
</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;">
<i class="bi bi-person-fill text-danger"></i>
</div>
<span class="d-none d-sm-inline fw-bold text-dark">{{ user.username }}</span>
</a>
<a href="{% url 'logout' %}" class="btn btn-outline-danger btn-sm">Logout</a>
<!-- Profile Dropdown -->
<div class="dropdown">
<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">
<div class="rounded-circle overflow-hidden border border-danger-subtle d-flex align-items-center justify-content-center" style="width: 35px; height: 35px;">
{% if user.profile.profile_pic %}
<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>
{% else %}
<div class="d-flex align-items-center gap-2">

View File

@ -45,8 +45,17 @@
{% 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="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;">
{{ donor.blood_group }}
<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: 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>
<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 %}
<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="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>
<h6 class="mb-0 fw-bold text-dark">
{{ donor.name }}
{% if donor.is_verified %}

View File

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

View File

@ -9,16 +9,20 @@
<div class="col-lg-8">
<div class="glass-card">
<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;">
<i class="bi bi-person-fill text-white fs-2"></i>
</div>
{% if user.profile.profile_pic %}
<img src="{{ user.profile.profile_pic.url }}" alt="{{ user.username }}" class="rounded-circle me-3 object-fit-cover" style="width: 64px; height: 64px;">
{% 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>
<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>
</div>
</div>
<form method="POST">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h5 class="fw-bold mb-3 border-bottom pb-2">Account Information</h5>
<div class="row g-3 mb-4">
@ -59,6 +63,10 @@
<label class="form-label small fw-bold">Location</label>
{{ p_form.location }}
</div>
<div class="col-12">
<label class="form-label small fw-bold">Profile Picture</label>
{{ p_form.profile_pic }}
</div>
</div>
<div class="d-grid">

View File

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

View File

@ -34,7 +34,7 @@
</div>
<div class="col-md-6">
<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 class="col-md-6">
<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,
vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request,
complete_donation, submit_feedback, notifications_view,
register_donor
complete_donation, notifications_view,
register_donor, hospital_list
)
urlpatterns = [
@ -25,7 +25,7 @@ urlpatterns = [
path("request-blood/", request_blood, name="request_blood"),
path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"),
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("register-donor/", register_donor, name="register_donor"),
path("hospitals/", hospital_list, name="hospital_list"),
]

View File

@ -1,5 +1,6 @@
import os
import platform
import math
from django.db import models
from django.shortcuts import render, redirect
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 import messages
from django.utils import timezone
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Feedback
from .forms import UserUpdateForm, ProfileUpdateForm
import math
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Hospital
from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm
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
def profile(request):
@ -18,7 +42,7 @@ def profile(request):
if request.method == 'POST':
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():
u_form.save()
p_form.save()
@ -67,13 +91,20 @@ def logout_view(request):
def register_view(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
form = UserRegisterForm(request.POST)
if form.is_valid():
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)
messages.success(request, f"Welcome to RaktaPulse, {user.username}! You are now a registered donor.")
return redirect("home")
else:
form = UserCreationForm()
form = UserRegisterForm()
return render(request, "core/register.html", {"form": form})
def home(request):
@ -276,6 +307,7 @@ def request_blood(request):
context = {
'blood_groups': [g[0] for g in BLOOD_GROUPS],
'urgency_levels': BloodRequest.URGENCY_LEVELS,
'selected_hospital': request.GET.get('hospital', ''),
}
return render(request, 'core/request_blood.html', context)
@ -355,12 +387,12 @@ def complete_donation(request, event_id):
# Notify both
Notification.objects.create(
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:
Notification.objects.create(
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!")
@ -369,21 +401,6 @@ def complete_donation(request, event_id):
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
def notifications_view(request):
notifications = Notification.objects.filter(user=request.user).order_by('-created_at')