Autosave: 20260217-162235

This commit is contained in:
Flatlogic Bot 2026-02-17 16:22:35 +00:00
parent d86257fa5a
commit 0f9878cadc
17 changed files with 272 additions and 26 deletions

Binary file not shown.

27
core/forms.py Normal file
View File

@ -0,0 +1,27 @@
from django import forms
from django.contrib.auth.models import User
from .models import UserProfile, BLOOD_GROUPS
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name']
widgets = {
'username': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
}
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['bio', 'location', 'phone', 'blood_group']
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),
}

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-02-17 16:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_bloodrequest_latitude_bloodrequest_longitude'),
]
operations = [
migrations.AddField(
model_name='bloodbank',
name='total_capacity',
field=models.IntegerField(default=1000),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-02-17 16:13
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_bloodbank_total_capacity'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bio', models.TextField(blank=True, max_length=500)),
('location', models.CharField(blank=True, max_length=100)),
('birth_date', models.DateField(blank=True, null=True)),
('phone', models.CharField(blank=True, max_length=20)),
('blood_group', models.CharField(blank=True, choices=[('A+', 'A+'), ('A-', 'A-'), ('B+', 'B+'), ('B-', 'B-'), ('O+', 'O+'), ('O-', 'O-'), ('AB+', 'AB+'), ('AB-', 'AB-')], max_length=5)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,6 +1,36 @@
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
BLOOD_GROUPS = [
('A+', 'A+'), ('A-', 'A-'),
('B+', 'B+'), ('B-', 'B-'),
('O+', 'O+'), ('O-', 'O-'),
('AB+', 'AB+'), ('AB-', 'AB-'),
]
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=100, blank=True)
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)
def __str__(self):
return self.user.username
@receiver(post_save, sender=User)
def create_or_save_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.get_or_create(user=instance)
else:
# For existing users, ensure profile exists
if not hasattr(instance, 'profile'):
UserProfile.objects.create(user=instance)
instance.profile.save()
class VaccineRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
@ -16,12 +46,6 @@ class VaccineRecord(models.Model):
return f"{self.vaccine_name} - Dose {self.dose_number} for {self.user.username}"
class Donor(models.Model):
BLOOD_GROUPS = [
('A+', 'A+'), ('A-', 'A-'),
('B+', 'B+'), ('B-', 'B-'),
('O+', 'O+'), ('O-', 'O-'),
('AB+', 'AB+'), ('AB-', 'AB-'),
]
name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
district = models.CharField(max_length=100, default='Kathmandu')
@ -48,7 +72,7 @@ class BloodRequest(models.Model):
('NORMAL', 'Normal'),
]
patient_name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=Donor.BLOOD_GROUPS)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
location = models.CharField(max_length=255)
urgency = models.CharField(max_length=10, choices=URGENCY_LEVELS, default='NORMAL')
hospital = models.CharField(max_length=255)
@ -77,6 +101,7 @@ class BloodBank(models.Model):
stock_o_minus = models.IntegerField(default=0)
stock_ab_plus = models.IntegerField(default=0)
stock_ab_minus = models.IntegerField(default=0)
total_capacity = models.IntegerField(default=1000)
def __str__(self):
return self.name

View File

@ -202,6 +202,7 @@
<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> 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> Vaccination</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> 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> My Records</a></li>
{% endif %}
<li><a href="/admin/"><i class="bi bi-gear-fill"></i> Settings</a></li>
@ -218,35 +219,40 @@
<!-- Page Content -->
<div id="content">
<!-- Top Bar -->
<div class="top-bar justify-content-between">
<div class="top-bar justify-content-between flex-wrap">
<div class="d-flex align-items-center">
<button type="button" id="sidebarCollapse" class="btn btn-link text-danger me-3 p-0">
<i class="bi bi-list fs-3"></i>
</button>
<div class="search-box d-none d-sm-flex">
<div class="search-box d-none d-lg-flex">
<i class="bi bi-search text-secondary"></i>
<input type="text" placeholder="Search anything...">
</div>
</div>
<div class="d-flex align-items-center gap-4">
<div id="location-status" class="d-none d-md-flex align-items-center text-secondary small cursor-pointer" style="cursor: pointer;" onclick="detectLocation()">
<i class="bi bi-geo-alt me-2"></i>
<div class="d-flex align-items-center gap-2 gap-md-4">
<div id="location-status" class="d-none d-xl-flex align-items-center text-secondary small cursor-pointer" style="cursor: pointer;" onclick="detectLocation()">
<i class="bi bi-geo-alt me-1"></i>
<span id="location-text">Detect Location</span>
</div>
<div class="d-none d-md-flex align-items-center text-secondary small">
<i class="bi bi-calendar3 me-2"></i>
{{ current_time|date:"D, M d, Y" }}
<i class="bi bi-calendar3 me-1"></i>
{{ current_time|date:"M d" }}
</div>
{% if user.is_authenticated %}
<div class="d-flex align-items-center gap-2">
<span class="d-none d-sm-inline fw-bold text-dark">{{ user.username }}</span>
<a href="/logout" class="btn btn-outline-danger btn-sm">Logout</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>
</div>
{% else %}
<div class="d-flex align-items-center gap-2">
<a href="/login" class="btn btn-danger btn-sm px-3">Login</a>
<a href="/register" class="btn btn-outline-danger btn-sm px-3">Register</a>
<a href="{% url 'login' %}" class="btn btn-danger btn-sm px-2 px-sm-3">Login</a>
<a href="{% url 'register' %}" class="btn btn-outline-danger btn-sm px-2 px-sm-3">Register</a>
</div>
{% endif %}
</div>

View File

@ -48,54 +48,78 @@
</a>
</p>
<h6 class="fw-bold text-dark mb-3">Inventory Levels (Units)</h6>
<h6 class="fw-bold text-dark mb-3">Inventory Levels (Max {{ bank.total_capacity }} units/type)</h6>
<div class="row g-2 mb-4">
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">A+</div>
<div class="fw-bold">{{ bank.stock_a_plus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_a_plus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">A-</div>
<div class="fw-bold">{{ bank.stock_a_minus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_a_minus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">B+</div>
<div class="fw-bold">{{ bank.stock_b_plus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_b_plus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">B-</div>
<div class="fw-bold">{{ bank.stock_b_minus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_b_minus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">O+</div>
<div class="fw-bold">{{ bank.stock_o_plus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_o_plus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">O-</div>
<div class="fw-bold">{{ bank.stock_o_minus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_o_minus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">AB+</div>
<div class="fw-bold">{{ bank.stock_ab_plus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_ab_plus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">AB-</div>
<div class="fw-bold">{{ bank.stock_ab_minus }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: {% widthratio bank.stock_ab_minus bank.total_capacity 100 %}%"></div>
</div>
</div>
</div>
</div>

View File

@ -133,8 +133,8 @@
<div class="icon-box bg-primary bg-opacity-10 text-primary">
<i class="bi bi-droplet"></i>
</div>
<div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">Units</small></div>
<div class="stat-label">Bank Inventory</div>
<div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">/ {{ stats.total_capacity }}</small></div>
<div class="stat-label">Inventory vs Capacity</div>
</div>
</div>
<div class="col-xl-3 col-md-6">

View File

@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}User Profile - RaktaPulse{% endblock %}
{% block content %}
<div class="container py-4">
<div class="row justify-content-center">
<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>
<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">
{% csrf_token %}
<h5 class="fw-bold mb-3 border-bottom pb-2">Account Information</h5>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold">Username</label>
{{ u_form.username }}
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">Email Address</label>
{{ u_form.email }}
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">First Name</label>
{{ u_form.first_name }}
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">Last Name</label>
{{ u_form.last_name }}
</div>
</div>
<h5 class="fw-bold mb-3 border-bottom pb-2">Profile Details</h5>
<div class="row g-3 mb-4">
<div class="col-12">
<label class="form-label small fw-bold">Bio</label>
{{ p_form.bio }}
<div class="form-text">Write a short bio about yourself.</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">Blood Group</label>
{{ p_form.blood_group }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">Phone</label>
{{ p_form.phone }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">Location</label>
{{ p_form.location }}
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-danger py-2 fw-bold">
<i class="bi bi-check-circle me-2"></i>Update Profile
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,12 +1,13 @@
from django.urls import path
from .views import home, login_view, logout_view, register_view, donor_list, blood_request_list, blood_bank_list, vaccination_info, vaccination_dashboard, add_vaccination, live_map
from .views import home, login_view, logout_view, register_view, donor_list, blood_request_list, blood_bank_list, vaccination_info, vaccination_dashboard, add_vaccination, live_map, request_blood, profile
urlpatterns = [
path("", home, name="home"),
path("login/", login_view, name="login"),
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"),
path("profile/", profile, name="profile"),
path("donors/", donor_list, name="donor_list"),
path("requests/", blood_request_list, name="blood_request_list"),
path("banks/", blood_bank_list, name="blood_bank_list"),

View File

@ -6,9 +6,34 @@ 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
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS
from .forms import UserUpdateForm, ProfileUpdateForm
import math
@login_required
def profile(request):
# Ensure user has a profile
profile, created = UserProfile.objects.get_or_create(user=request.user)
if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user)
p_form = ProfileUpdateForm(request.POST, instance=profile)
if u_form.is_valid() and p_form.is_valid():
u_form.save()
p_form.save()
messages.success(request, f'Your account has been updated!')
return redirect('profile')
else:
u_form = UserUpdateForm(instance=request.user)
p_form = ProfileUpdateForm(instance=profile)
context = {
'u_form': u_form,
'p_form': p_form
}
return render(request, 'core/profile.html', context)
def haversine(lat1, lon1, lat2, lon2):
# Radius of the Earth in km
R = 6371.0
@ -92,6 +117,7 @@ def home(request):
bb.stock_o_plus + bb.stock_o_minus + bb.stock_ab_plus + bb.stock_ab_minus
for bb in blood_banks
]),
"total_capacity": sum([bb.total_capacity * 8 for bb in blood_banks]), # 8 blood types
"vaccinated_percentage": 0
}
@ -104,7 +130,7 @@ def home(request):
"donors": donor_list_data[:8],
"blood_requests": blood_requests[:6],
"blood_banks": blood_banks,
"blood_groups": [g[0] for g in Donor.BLOOD_GROUPS],
"blood_groups": [g[0] for g in BLOOD_GROUPS],
"stats": stats,
"project_name": "RaktaPulse",
"current_time": timezone.now(),
@ -142,7 +168,7 @@ def donor_list(request):
context = {
'donors': donor_list_data,
'blood_groups': [g[0] for g in Donor.BLOOD_GROUPS],
'blood_groups': [g[0] for g in BLOOD_GROUPS],
}
return render(request, 'core/donor_list.html', context)
@ -231,7 +257,7 @@ def request_blood(request):
messages.error(request, "Please fill in all required fields.")
context = {
'blood_groups': [g[0] for g in Donor.BLOOD_GROUPS],
'blood_groups': [g[0] for g in BLOOD_GROUPS],
'urgency_levels': BloodRequest.URGENCY_LEVELS,
}
return render(request, 'core/request_blood.html', context)

17
reduce_capacity.py Normal file
View File

@ -0,0 +1,17 @@
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django.setup()
from core.models import BloodBank
def run():
banks = BloodBank.objects.all()
for bank in banks:
bank.total_capacity = 200 # Reduced from 1000
bank.save()
print("Capacity reduced for all blood banks.")
if __name__ == "__main__":
run()