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 %} + {% 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 @@

    Last Donated: {{ donor.last_donation_date|default:"Never" }}

    - Contact +
    + {% if donor.user %} + + + + {% endif %} + Call +
    {% empty %} @@ -312,7 +319,14 @@

    {{ req.hospital }}

    {{ req.created_at|timesince }} ago - Help Now → +
    + {% if req.user %} + + + + {% endif %} + 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 %} + {{ profile_user.username }} + {% 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 }} +
    +
    + +
    + {% if user.is_authenticated and user != profile_user %} + + Send Message + + {% endif %} + + Back to Donors + +
    +
    +
    +
    +
    +{% 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})