diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc
index 404de93..14cf095 100644
Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ
diff --git a/config/settings.py b/config/settings.py
index cd49a45..0e1371a 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -86,6 +86,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages',
# IMPORTANT: do not remove – injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
'core.context_processors.project_context',
+ 'core.context_processors.unread_messages',
],
},
},
diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc
index 10556ae..db70f1c 100644
Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ
diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc
index 3d8eb68..2abb700 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index 116ac38..0c55c43 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index 2dedc28..51dcfcb 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/context_processors.py b/core/context_processors.py
index 0bf87c3..4bae31f 100644
--- a/core/context_processors.py
+++ b/core/context_processors.py
@@ -1,5 +1,13 @@
import os
import time
+from .models import Message
+
+def unread_messages(request):
+ if request.user.is_authenticated:
+ return {
+ 'unread_messages_count': Message.objects.filter(receiver=request.user, is_read=False).count()
+ }
+ return {'unread_messages_count': 0}
def project_context(request):
"""
diff --git a/core/migrations/0014_message.py b/core/migrations/0014_message.py
new file mode 100644
index 0000000..8fd2829
--- /dev/null
+++ b/core/migrations/0014_message.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.2.7 on 2026-02-18 07:37
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0013_userprofile_profile_pic'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Message',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('content', models.TextField()),
+ ('timestamp', models.DateTimeField(auto_now_add=True)),
+ ('is_read', models.BooleanField(default=False)),
+ ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)),
+ ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/core/migrations/__pycache__/0014_message.cpython-311.pyc b/core/migrations/__pycache__/0014_message.cpython-311.pyc
new file mode 100644
index 0000000..7665b75
Binary files /dev/null and b/core/migrations/__pycache__/0014_message.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 60feb33..2b82091 100644
--- a/core/models.py
+++ b/core/models.py
@@ -148,3 +148,13 @@ class Notification(models.Model):
def __str__(self):
return f"Notification for {self.user.username}: {self.message[:20]}..."
+
+class Message(models.Model):
+ sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages')
+ receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_messages')
+ content = models.TextField()
+ timestamp = models.DateTimeField(auto_now_add=True)
+ is_read = models.BooleanField(default=False)
+
+ def __str__(self):
+ return f"From {self.sender.username} to {self.receiver.username} at {self.timestamp}"
diff --git a/core/templates/base.html b/core/templates/base.html
index 70f3723..9d11911 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -275,8 +275,8 @@
{% trans "Dashboard" %}
{% trans "Donors" %}
{% trans "Blood Requests" %}
- {% trans "Blood Banks" %}
- {% trans "Hospitals" %}
+ {% trans "Blood Banks" %}
+ {% trans "Hospitals" %}
{% trans "Live Alerts" %}
{% trans "Vaccination" %}
{% if user.is_authenticated %}
@@ -343,6 +343,16 @@
{{ user.notifications.count }}
+
+
+
+
+ {% if unread_messages_count > 0 %}
+
+ {{ unread_messages_count }}
+
+ {% endif %}
+
diff --git a/core/templates/core/blood_request_list.html b/core/templates/core/blood_request_list.html
index 1442784..8d716ff 100644
--- a/core/templates/core/blood_request_list.html
+++ b/core/templates/core/blood_request_list.html
@@ -52,6 +52,11 @@
{{ req.created_at|timesince }} ago
diff --git a/core/templates/core/chat.html b/core/templates/core/chat.html
new file mode 100644
index 0000000..872adbe
--- /dev/null
+++ b/core/templates/core/chat.html
@@ -0,0 +1,353 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}Chat with {{ other_user.username }} - RaktaPulse{% endblock %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% if other_user.profile.profile_pic %}
+

+ {% else %}
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+ {% for msg in chat_messages %}
+
+ {{ msg.content }}
+
+ {{ msg.timestamp|date:"g:i a" }}
+
+
+ {% empty %}
+
+
No messages yet. Say hi!
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ other_user.username }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% trans "Incoming Call" %}
+ {{ other_user.username }} {% trans "is calling..." %}
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+
+{% endblock %}
diff --git a/core/templates/core/donor_list.html b/core/templates/core/donor_list.html
index 48e66c0..738a14d 100644
--- a/core/templates/core/donor_list.html
+++ b/core/templates/core/donor_list.html
@@ -79,7 +79,17 @@
-
Contact Now
+
+ {% if donor.user %}
+
+
+
+
+
+
+ {% endif %}
+
Call
+
{% endfor %}
diff --git a/core/templates/core/hospital_list.html b/core/templates/core/hospital_list.html
index 8e01635..edf2eb5 100644
--- a/core/templates/core/hospital_list.html
+++ b/core/templates/core/hospital_list.html
@@ -50,7 +50,7 @@
{% endif %}
- Request Blood Here
+ Request Blood Here
diff --git a/core/templates/core/inbox.html b/core/templates/core/inbox.html
new file mode 100644
index 0000000..025d72a
--- /dev/null
+++ b/core/templates/core/inbox.html
@@ -0,0 +1,54 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}Inbox - RaktaPulse{% endblock %}
+
+{% block content %}
+
+
+
Messages
+
+
+
+ {% if conversations %}
+
+ {% else %}
+
+
+
+
+
No messages yet
+
Start a conversation with a donor or requester!
+
Find Donors
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index 378a591..4b4a9c5 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -257,7 +257,14 @@
- Contact
+
+ {% if donor.user %}
+
+
+
+ {% endif %}
+
Call
+
{% empty %}
@@ -312,7 +319,14 @@
{{ req.created_at|timesince }} ago
-
Help Now →
+
{% empty %}
diff --git a/core/templates/core/public_profile.html b/core/templates/core/public_profile.html
new file mode 100644
index 0000000..c04758e
--- /dev/null
+++ b/core/templates/core/public_profile.html
@@ -0,0 +1,72 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}{{ profile_user.username }}'s Profile - RaktaPulse{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% if profile.profile_pic %}
+

+ {% else %}
+
+
+
+ {% endif %}
+
+
{{ profile_user.first_name }} {{ profile_user.last_name }}
+
@{{ profile_user.username }}
+
+ {% if donor %}
+
+ {{ donor.blood_group }}
+ {% if donor.is_available %}
+ Available
+ {% else %}
+ Not Available
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+
+
Location
+
{{ profile.location|default:"Not specified" }}
+
+
+
+
+
Member Since
+
{{ profile_user.date_joined|date:"F Y" }}
+
+
+
+
+
+
About
+
+ {{ profile.bio|default:"No bio provided."|linebreaks }}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/core/urls.py b/core/urls.py
index 9d3763d..c1f8813 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -6,7 +6,7 @@ from .views import (
vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request,
complete_donation, notifications_view,
- register_donor, hospital_list
+ register_donor, hospital_list, public_profile, inbox, chat
)
urlpatterns = [
@@ -15,6 +15,9 @@ urlpatterns = [
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"),
path("profile/", profile, name="profile"),
+ path("profile//", public_profile, name="public_profile"),
+ path("inbox/", inbox, name="inbox"),
+ path("chat//", chat, name="chat"),
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"),
diff --git a/core/views.py b/core/views.py
index de394c0..f3f8d34 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,14 +1,16 @@
import os
import platform
import math
-from django.db import models
+from django.db.models import Q
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate
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, Hospital
+from django.contrib.auth.models import User
+from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Hospital, Message
+
from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm
def hospital_list(request):
@@ -171,7 +173,7 @@ def home(request):
if request.user.is_authenticated:
# Get active involvements (where user is donor or requester)
involved_events = DonationEvent.objects.filter(
- (models.Q(donor_user=request.user) | models.Q(request__user=request.user)),
+ (Q(donor_user=request.user) | Q(request__user=request.user)),
is_completed=False
)
context["involved_events"] = involved_events
@@ -434,3 +436,63 @@ def register_donor(request):
messages.error(request, "Please fill in all required fields.")
return render(request, 'core/register_donor.html', {'blood_groups': [g[0] for g in BLOOD_GROUPS]})
+
+def public_profile(request, username):
+ user = User.objects.get(username=username)
+ profile = user.profile
+ donor_profile = getattr(user, 'donor_profile', None)
+
+ context = {
+ 'profile_user': user,
+ 'profile': profile,
+ 'donor': donor_profile,
+ }
+ return render(request, 'core/public_profile.html', context)
+
+@login_required
+def inbox(request):
+ # Get all users the current user has messaged or received messages from
+ sent_to = Message.objects.filter(sender=request.user).values_list('receiver', flat=True)
+ received_from = Message.objects.filter(receiver=request.user).values_list('sender', flat=True)
+ user_ids = set(list(sent_to) + list(received_from))
+
+ users = User.objects.filter(id__in=user_ids)
+
+ # Get last message for each conversation
+ conversations = []
+ for user in users:
+ last_message = Message.objects.filter(
+ (Q(sender=request.user) & Q(receiver=user)) |
+ (Q(sender=user) & Q(receiver=request.user))
+ ).order_by('-timestamp').first()
+ conversations.append({
+ 'user': user,
+ 'last_message': last_message
+ })
+
+ conversations.sort(key=lambda x: x['last_message'].timestamp, reverse=True)
+
+ return render(request, 'core/inbox.html', {'conversations': conversations})
+
+@login_required
+def chat(request, username):
+ other_user = User.objects.get(username=username)
+ if request.method == "POST":
+ content = request.POST.get('content')
+ if content:
+ Message.objects.create(
+ sender=request.user,
+ receiver=other_user,
+ content=content
+ )
+ return redirect('chat', username=username)
+
+ messages = Message.objects.filter(
+ (Q(sender=request.user) & Q(receiver=other_user)) |
+ (Q(sender=other_user) & Q(receiver=request.user))
+ ).order_by('timestamp')
+
+ # Mark as read
+ Message.objects.filter(sender=other_user, receiver=request.user, is_read=False).update(is_read=True)
+
+ return render(request, 'core/chat.html', {'other_user': other_user, 'chat_messages': messages})