581 lines
22 KiB
Python
581 lines
22 KiB
Python
import os
|
|
import platform
|
|
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.utils import timezone
|
|
from .models import (
|
|
Profile, Intent, ValueTag, Message, Post, Comment, Reaction,
|
|
HiddenPost, Follow, ConnectionRequest, Like, Match, Thread, Block,
|
|
Event, RSVP, EventTag, EventInvite
|
|
)
|
|
from .forms import EventForm
|
|
from django.contrib.auth.models import User
|
|
from django.contrib.auth.forms import UserCreationForm
|
|
from django.contrib.auth import login
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import Q
|
|
|
|
def get_dashboard_context(request):
|
|
"""Helper to get consistent context for dashboard-like views."""
|
|
stats = {}
|
|
suggested_members = []
|
|
suggested_events = []
|
|
following_ids = []
|
|
|
|
if request.user.is_authenticated:
|
|
# Quick Stats
|
|
stats = {
|
|
'unread_messages': Message.objects.filter(recipient=request.user, is_read=False).count(),
|
|
'pending_connections': ConnectionRequest.objects.filter(to_user=request.user, status='pending').count(),
|
|
'upcoming_events_count': request.user.rsvps.filter(status='going', event__start_datetime__gt=timezone.now()).count(),
|
|
'completion_percentage': request.user.profile.profile_completion_percentage,
|
|
'streak': request.user.profile.accountability_streak,
|
|
'followers_count': request.user.profile.followers_count,
|
|
'following_count': request.user.profile.following_count,
|
|
}
|
|
|
|
following_ids = list(Follow.objects.filter(follower=request.user).values_list('followed_id', flat=True))
|
|
|
|
# Suggestions
|
|
user_intents = request.user.profile.intents.all()
|
|
user_values = request.user.profile.value_tags.all()
|
|
suggested_members = Profile.objects.filter(
|
|
Q(intents__in=user_intents) | Q(value_tags__in=user_values)
|
|
).exclude(user=request.user).distinct()[:10]
|
|
|
|
# Suggested Events
|
|
suggested_events = Event.objects.filter(start_datetime__gt=timezone.now()).order_by('start_datetime')[:5]
|
|
|
|
return {
|
|
"stats": stats,
|
|
"suggested_members": suggested_members,
|
|
"suggested_events": suggested_events,
|
|
"following_ids": following_ids,
|
|
"post_types": Post.POST_TYPE_CHOICES,
|
|
"current_time": timezone.now(),
|
|
"project_name": "CommonGround",
|
|
}
|
|
|
|
def home(request):
|
|
"""Render the landing screen or member dashboard."""
|
|
|
|
# Simple logic to seed data for the first run
|
|
if not Intent.objects.exists():
|
|
intents_data = [
|
|
('Friendship', 'bi-people'),
|
|
('Networking', 'bi-briefcase'),
|
|
('Activity Partner', 'bi-bicycle'),
|
|
('Accountability', 'bi-check-circle')
|
|
]
|
|
for name, icon in intents_data:
|
|
Intent.objects.create(name=name, icon=icon)
|
|
|
|
if not Profile.objects.exists():
|
|
# Create a demo user/profile
|
|
demo_user, _ = User.objects.get_or_create(username='marcus_v', first_name='Marcus', last_name='V.')
|
|
p = Profile.objects.create(
|
|
user=demo_user,
|
|
professional_headline='Architect & Urban Planner',
|
|
transition_status='new-in-town',
|
|
bio='Passionate about sustainable cities. Recently moved here from Chicago and looking for local communities.',
|
|
location_city='Austin, TX',
|
|
)
|
|
p.intents.add(Intent.objects.get(name='Networking'))
|
|
|
|
demo_user2, _ = User.objects.get_or_create(username='sarah_l', first_name='Sarah', last_name='L.')
|
|
p2 = Profile.objects.create(
|
|
user=demo_user2,
|
|
professional_headline='UX Researcher | Growth Mindset',
|
|
transition_status='post-divorce',
|
|
bio='Rediscovering my love for hiking and photography. Seeking authentic connections and shared growth.',
|
|
location_city='Austin, TX',
|
|
)
|
|
p2.intents.add(Intent.objects.get(name='Friendship'))
|
|
|
|
# Social Feed Logic
|
|
hidden_post_ids = []
|
|
if request.user.is_authenticated:
|
|
hidden_post_ids = HiddenPost.objects.filter(user=request.user).values_list('post_id', flat=True)
|
|
|
|
posts = Post.objects.exclude(id__in=hidden_post_ids).select_related('author', 'author__profile').prefetch_related('comments', 'comments__author', 'reactions')
|
|
|
|
# Filtering by intent (for discovery)
|
|
intent_filter = request.GET.get('intent')
|
|
if intent_filter:
|
|
profiles = Profile.objects.filter(intents__name__iexact=intent_filter)
|
|
else:
|
|
profiles = Profile.objects.all()
|
|
|
|
intents = Intent.objects.all()
|
|
|
|
context = get_dashboard_context(request)
|
|
context.update({
|
|
"profiles": profiles,
|
|
"intents": intents,
|
|
"current_intent": intent_filter,
|
|
"posts": posts,
|
|
})
|
|
return render(request, "core/index.html", context)
|
|
|
|
@login_required
|
|
def create_post(request):
|
|
if request.method == 'POST':
|
|
content = request.POST.get('content')
|
|
image = request.FILES.get('image')
|
|
post_type = request.POST.get('post_type', 'reflection')
|
|
if content or image:
|
|
Post.objects.create(author=request.user, content=content, image=image, post_type=post_type)
|
|
return redirect('home')
|
|
|
|
@login_required
|
|
def delete_post(request, post_id):
|
|
post = get_object_or_404(Post, id=post_id, author=request.user)
|
|
post.delete()
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def add_comment(request, post_id):
|
|
if request.method == 'POST':
|
|
post = get_object_or_404(Post, id=post_id)
|
|
content = request.POST.get('content')
|
|
if content:
|
|
Comment.objects.create(post=post, author=request.user, content=content)
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def toggle_reaction(request, post_id):
|
|
post = get_object_or_404(Post, id=post_id)
|
|
reaction_type = request.GET.get('type', 'heart')
|
|
reaction, created = Reaction.objects.get_or_create(
|
|
post=post, user=request.user, reaction_type=reaction_type
|
|
)
|
|
if not created:
|
|
reaction.delete()
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def toggle_follow(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
if target_user == request.user:
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
follow, created = Follow.objects.get_or_create(follower=request.user, followed=target_user)
|
|
if not created:
|
|
follow.delete()
|
|
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def hide_post(request, post_id):
|
|
post = get_object_or_404(Post, id=post_id)
|
|
HiddenPost.objects.get_or_create(user=request.user, post=post)
|
|
return redirect('home')
|
|
|
|
def about(request):
|
|
return render(request, "core/about.html")
|
|
|
|
def signup(request):
|
|
if request.method == 'POST':
|
|
form = UserCreationForm(request.POST)
|
|
if form.is_valid():
|
|
user = form.save()
|
|
# Create profile
|
|
Profile.objects.get_or_create(user=user)
|
|
login(request, user)
|
|
return redirect('onboarding')
|
|
else:
|
|
form = UserCreationForm()
|
|
return render(request, 'registration/signup.html', {'form': form})
|
|
|
|
@login_required
|
|
def onboarding(request):
|
|
profile = request.user.profile
|
|
if request.method == 'POST':
|
|
profile.gamer_tag = request.POST.get('gamer_tag', '')
|
|
profile.platform = request.POST.get('platform', 'pc')
|
|
profile.primary_game_id = request.POST.get('primary_game')
|
|
profile.bio = request.POST.get('bio', '')
|
|
profile.onboarding_completed = True
|
|
profile.save()
|
|
return redirect('home')
|
|
|
|
from .models import Game
|
|
games = Game.objects.all().order_by('name')
|
|
return render(request, 'core/onboarding.html', {
|
|
'profile': profile,
|
|
'games': games,
|
|
'platforms': profile.PLATFORM_CHOICES
|
|
})
|
|
|
|
@login_required
|
|
def settings_view(request):
|
|
profile = request.user.profile
|
|
if request.method == 'POST':
|
|
profile.two_factor_enabled = 'two_factor' in request.POST
|
|
profile.save()
|
|
return redirect('settings')
|
|
return render(request, 'core/settings.html', {'profile': profile})
|
|
|
|
def get_started(request):
|
|
if not request.user.is_authenticated:
|
|
return redirect('signup')
|
|
if not request.user.profile.onboarding_completed:
|
|
return redirect('onboarding')
|
|
return redirect('home')
|
|
|
|
@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('recipient', flat=True)
|
|
received_from = Message.objects.filter(recipient=request.user).values_list('sender', flat=True)
|
|
|
|
partner_ids = set(list(sent_to) + list(received_from))
|
|
partners = User.objects.filter(id__in=partner_ids).select_related('profile')
|
|
|
|
for partner in partners:
|
|
last_message = Message.objects.filter(
|
|
Q(sender=request.user, recipient=partner) |
|
|
Q(sender=partner, recipient=request.user)
|
|
).order_by('-timestamp').first()
|
|
partner.last_message = last_message
|
|
|
|
return render(request, 'core/inbox.html', {'partners': partners})
|
|
|
|
@login_required
|
|
def chat_detail(request, username):
|
|
partner = get_object_or_404(User, username=username)
|
|
if partner == request.user:
|
|
return redirect('inbox')
|
|
|
|
thread = Thread.objects.filter(participants=request.user).filter(participants=partner).first()
|
|
if not thread:
|
|
thread = Thread.objects.create()
|
|
thread.participants.add(request.user, partner)
|
|
|
|
if request.method == 'POST':
|
|
body = request.POST.get('body')
|
|
if body:
|
|
Message.objects.create(sender=request.user, recipient=partner, body=body, thread=thread)
|
|
thread.updated_at = timezone.now()
|
|
thread.save()
|
|
return redirect('chat_detail', username=username)
|
|
|
|
messages = Message.objects.filter(thread=thread).order_by('timestamp')
|
|
messages.filter(recipient=request.user, is_read=False).update(is_read=True)
|
|
|
|
return render(request, 'core/chat.html', {
|
|
'partner': partner,
|
|
'chat_messages': messages,
|
|
'thread': thread
|
|
})
|
|
|
|
@login_required
|
|
def profile_view(request):
|
|
return redirect('profile_detail', username=request.user.username)
|
|
|
|
def profile_detail(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
is_following = False
|
|
if request.user.is_authenticated:
|
|
is_following = Follow.objects.filter(follower=request.user, followed=target_user).exists()
|
|
return render(request, 'core/profile_detail.html', {
|
|
'target_user': target_user,
|
|
'is_following': is_following
|
|
})
|
|
|
|
@login_required
|
|
def edit_profile(request):
|
|
profile = request.user.profile
|
|
if request.method == 'POST':
|
|
profile.professional_headline = request.POST.get('headline', '')
|
|
profile.bio = request.POST.get('bio', '')
|
|
profile.location_city = request.POST.get('location', '')
|
|
profile.platform = request.POST.get('platform', '')
|
|
profile.gamer_tag = request.POST.get('gamer_tag', '')
|
|
profile.rank = request.POST.get('rank', '')
|
|
profile.preferred_role = request.POST.get('preferred_role', '')
|
|
|
|
primary_game_id = request.POST.get('primary_game')
|
|
if primary_game_id:
|
|
profile.primary_game_id = primary_game_id
|
|
|
|
if 'avatar' in request.FILES:
|
|
profile.avatar = request.FILES['avatar']
|
|
profile.save()
|
|
return redirect('my_profile')
|
|
|
|
from .models import Game
|
|
games = Game.objects.all().order_by('name')
|
|
return render(request, 'core/edit_profile.html', {
|
|
'profile': profile,
|
|
'games': games,
|
|
'platforms': profile.PLATFORM_CHOICES
|
|
})
|
|
|
|
@login_required
|
|
def matches(request, tab=None):
|
|
"""Main hub for 'My Matches'."""
|
|
if not tab:
|
|
tab = request.GET.get('tab', 'mutual')
|
|
search_query = request.GET.get('q', '')
|
|
intent_filter = request.GET.get('intent', '')
|
|
sort_by = request.GET.get('sort', 'newest')
|
|
|
|
user = request.user
|
|
|
|
if tab == 'requests':
|
|
queryset = User.objects.filter(requests_sent__to_user=user, requests_sent__status='pending')
|
|
elif tab == 'sent':
|
|
queryset = User.objects.filter(requests_received__from_user=user, requests_received__status='pending')
|
|
elif tab == 'liked':
|
|
queryset = User.objects.filter(likes_received__from_user=user).exclude(
|
|
Q(matches_a__user_b=user) | Q(matches_b__user_a=user)
|
|
)
|
|
elif tab == 'blocked':
|
|
queryset = User.objects.filter(blocks_received__blocker=user)
|
|
else: # mutual
|
|
queryset = User.objects.filter(
|
|
Q(matches_a__user_b=user, matches_a__status='active') |
|
|
Q(matches_b__user_a=user, matches_b__status='active')
|
|
)
|
|
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(first_name__icontains=search_query) |
|
|
Q(last_name__icontains=search_query) |
|
|
Q(username__icontains=search_query) |
|
|
Q(profile__professional_headline__icontains=search_query) |
|
|
Q(profile__location_city__icontains=search_query)
|
|
)
|
|
|
|
if intent_filter:
|
|
queryset = queryset.filter(profile__intents__name=intent_filter)
|
|
|
|
if sort_by == 'newest':
|
|
queryset = queryset.order_by('-date_joined')
|
|
elif sort_by == 'aligned':
|
|
queryset = queryset.order_by('-profile__accountability_streak')
|
|
|
|
queryset = queryset.select_related('profile').prefetch_related('profile__intents').distinct()
|
|
|
|
context = get_dashboard_context(request)
|
|
incoming_requests = ConnectionRequest.objects.filter(to_user=user, status='pending')
|
|
request_map = {r.from_user_id: r.id for r in incoming_requests}
|
|
|
|
context.update({
|
|
'matches_list': queryset,
|
|
'current_tab': tab,
|
|
'search_query': search_query,
|
|
'current_intent': intent_filter,
|
|
'current_sort': sort_by,
|
|
'intents': Intent.objects.all(),
|
|
'title': 'My Matches',
|
|
'request_map': request_map
|
|
})
|
|
return render(request, 'core/matches.html', context)
|
|
|
|
@login_required
|
|
def send_match_request(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
if target_user == request.user:
|
|
return redirect('home')
|
|
|
|
if Like.objects.filter(from_user=target_user, to_user=request.user).exists():
|
|
Match.objects.get_or_create(
|
|
user_a=min(request.user, target_user, key=lambda u: u.id),
|
|
user_b=max(request.user, target_user, key=lambda u: u.id)
|
|
)
|
|
return redirect('matches')
|
|
|
|
ConnectionRequest.objects.get_or_create(from_user=request.user, to_user=target_user, status='pending')
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def handle_match_request(request, request_id):
|
|
conn_request = get_object_or_404(ConnectionRequest, id=request_id, to_user=request.user)
|
|
action = request.GET.get('action')
|
|
|
|
if action == 'accept':
|
|
conn_request.status = 'accepted'
|
|
conn_request.responded_at = timezone.now()
|
|
conn_request.save()
|
|
|
|
Match.objects.get_or_create(
|
|
user_a=min(conn_request.from_user, conn_request.to_user, key=lambda u: u.id),
|
|
user_b=max(conn_request.from_user, conn_request.to_user, key=lambda u: u.id)
|
|
)
|
|
elif action == 'decline':
|
|
conn_request.status = 'declined'
|
|
conn_request.responded_at = timezone.now()
|
|
conn_request.save()
|
|
|
|
return redirect('matches')
|
|
|
|
@login_required
|
|
def cancel_match_request(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
ConnectionRequest.objects.filter(from_user=request.user, to_user=target_user, status='pending').delete()
|
|
return redirect('matches')
|
|
|
|
@login_required
|
|
def block_user(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
Block.objects.get_or_create(blocker=request.user, blocked=target_user)
|
|
Match.objects.filter(
|
|
Q(user_a=request.user, user_b=target_user) |
|
|
Q(user_a=target_user, user_b=request.user)
|
|
).delete()
|
|
ConnectionRequest.objects.filter(
|
|
Q(from_user=request.user, to_user=target_user) |
|
|
Q(from_user=target_user, to_user=request.user)
|
|
).delete()
|
|
|
|
return redirect('matches')
|
|
|
|
@login_required
|
|
def toggle_like(request, username):
|
|
target_user = get_object_or_404(User, username=username)
|
|
if target_user == request.user:
|
|
return redirect('home')
|
|
|
|
like, created = Like.objects.get_or_create(from_user=request.user, to_user=target_user)
|
|
if not created:
|
|
like.delete()
|
|
else:
|
|
if Like.objects.filter(from_user=target_user, to_user=request.user).exists():
|
|
Match.objects.get_or_create(
|
|
user_a=min(request.user, target_user, key=lambda u: u.id),
|
|
user_b=max(request.user, target_user, key=lambda u: u.id)
|
|
)
|
|
|
|
return redirect(request.META.get('HTTP_REFERER', 'home'))
|
|
|
|
@login_required
|
|
def groups(request):
|
|
return render(request, 'core/placeholder.html', {'title': 'Groups'})
|
|
|
|
@login_required
|
|
def my_posts(request):
|
|
posts = Post.objects.filter(author=request.user).select_related('author', 'author__profile').prefetch_related('comments', 'reactions')
|
|
context = get_dashboard_context(request)
|
|
context.update({
|
|
'posts': posts,
|
|
'title': 'My Posts'
|
|
})
|
|
return render(request, 'core/index.html', context)
|
|
|
|
@login_required
|
|
def events(request):
|
|
tab = request.GET.get('tab', 'upcoming')
|
|
query = request.GET.get('q', '')
|
|
|
|
events = Event.objects.all().order_by('start_datetime')
|
|
|
|
if query:
|
|
events = events.filter(
|
|
Q(title__icontains=query) | Q(description__icontains=query) | Q(location_name__icontains=query)
|
|
)
|
|
|
|
if tab == 'mine':
|
|
events = events.filter(creator=request.user)
|
|
elif tab == 'attending':
|
|
events = events.filter(rsvps__user=request.user, rsvps__status='going')
|
|
elif tab == 'past':
|
|
events = Event.objects.filter(start_datetime__lt=timezone.now()).order_by('-start_datetime')
|
|
elif tab == 'calendar':
|
|
# Simple grouping by date for calendar view
|
|
events = events.filter(start_datetime__gt=timezone.now())
|
|
else: # upcoming
|
|
events = events.filter(start_datetime__gt=timezone.now())
|
|
|
|
context = get_dashboard_context(request)
|
|
|
|
# For calendar view, group events by date
|
|
calendar_events = {}
|
|
if tab == 'calendar':
|
|
for event in events:
|
|
date_key = event.start_datetime.date()
|
|
if date_key not in calendar_events:
|
|
calendar_events[date_key] = []
|
|
calendar_events[date_key].append(event)
|
|
|
|
context.update({
|
|
'events': events,
|
|
'calendar_events': calendar_events,
|
|
'tab': tab,
|
|
'query': query,
|
|
})
|
|
return render(request, 'core/events_list.html', context)
|
|
|
|
@login_required
|
|
def event_detail(request, event_id):
|
|
event = get_object_or_404(Event, id=event_id)
|
|
user_rsvp = RSVP.objects.filter(event=event, user=request.user).first()
|
|
rsvps = event.rsvps.select_related('user', 'user__profile')
|
|
|
|
context = get_dashboard_context(request)
|
|
context.update({
|
|
'event': event,
|
|
'user_rsvp': user_rsvp,
|
|
'rsvps': rsvps,
|
|
'going_count': rsvps.filter(status='going').count(),
|
|
'maybe_count': rsvps.filter(status='maybe').count(),
|
|
})
|
|
return render(request, 'core/event_detail.html', context)
|
|
|
|
@login_required
|
|
def event_create(request):
|
|
if request.method == 'POST':
|
|
form = EventForm(request.POST, request.FILES)
|
|
if form.is_valid():
|
|
event = form.save(commit=False)
|
|
event.creator = request.user
|
|
event.save()
|
|
form.save_m2m()
|
|
return redirect('event_detail', event_id=event.id)
|
|
else:
|
|
form = EventForm()
|
|
|
|
context = get_dashboard_context(request)
|
|
context.update({'form': form, 'title': 'Create Session'})
|
|
return render(request, 'core/event_form.html', context)
|
|
|
|
@login_required
|
|
def event_edit(request, event_id):
|
|
event = get_object_or_404(Event, id=event_id, creator=request.user)
|
|
if request.method == 'POST':
|
|
form = EventForm(request.POST, request.FILES, instance=event)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('event_detail', event_id=event.id)
|
|
else:
|
|
form = EventForm(instance=event)
|
|
|
|
context = get_dashboard_context(request)
|
|
context.update({'form': form, 'title': 'Edit Session', 'event': event})
|
|
return render(request, 'core/event_form.html', context)
|
|
|
|
@login_required
|
|
def event_delete(request, event_id):
|
|
event = get_object_or_404(Event, id=event_id, creator=request.user)
|
|
if request.method == 'POST':
|
|
event.delete()
|
|
return redirect('events')
|
|
return render(request, 'core/event_confirm_delete.html', {'event': event})
|
|
|
|
@login_required
|
|
def event_rsvp(request, event_id):
|
|
if request.method == 'POST':
|
|
event = get_object_or_404(Event, id=event_id)
|
|
status = request.POST.get('status')
|
|
if status in ['going', 'maybe', 'not_going']:
|
|
rsvp, created = RSVP.objects.update_or_create(
|
|
event=event, user=request.user,
|
|
defaults={'status': status}
|
|
)
|
|
elif status == 'cancel':
|
|
RSVP.objects.filter(event=event, user=request.user).delete()
|
|
|
|
return redirect(request.META.get('HTTP_REFERER', 'event_detail', event_id=event.id))
|
|
return redirect('events')
|