365 lines
14 KiB
Python
365 lines
14 KiB
Python
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
|
|
class Intent(models.Model):
|
|
name = models.CharField(max_length=100)
|
|
icon = models.CharField(max_length=50, blank=True, help_text="Bootstrap icon class name")
|
|
|
|
def __str__(self):
|
|
return str(self.name)
|
|
|
|
class ValueTag(models.Model):
|
|
name = models.CharField(max_length=100)
|
|
|
|
def __str__(self):
|
|
return str(self.name)
|
|
|
|
class Game(models.Model):
|
|
GENRE_CHOICES = [
|
|
('fps', 'FPS'),
|
|
('moba', 'MOBA'),
|
|
('battle_royale', 'Battle Royale'),
|
|
('sports', 'Sports'),
|
|
('mmo', 'MMO'),
|
|
('fighting', 'Fighting'),
|
|
('rpg', 'RPG'),
|
|
('strategy', 'Strategy'),
|
|
('other', 'Other'),
|
|
]
|
|
name = models.CharField(max_length=255, unique=True)
|
|
slug = models.SlugField(max_length=255, unique=True)
|
|
genre = models.CharField(max_length=50, choices=GENRE_CHOICES)
|
|
team_size = models.PositiveIntegerField(null=True, blank=True)
|
|
has_roles = models.BooleanField(default=False)
|
|
roles_json = models.JSONField(null=True, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return str(self.name)
|
|
|
|
class Profile(models.Model):
|
|
PLATFORM_CHOICES = [
|
|
('pc', 'PC'),
|
|
('playstation', 'PlayStation'),
|
|
('xbox', 'Xbox'),
|
|
('nintendo', 'Nintendo Switch'),
|
|
('mobile', 'Mobile'),
|
|
]
|
|
TRANSITION_CHOICES = [
|
|
('none', 'Stable'),
|
|
('post-divorce', 'Post-Divorce'),
|
|
('relocating', 'Relocating'),
|
|
('career-change', 'Career Change'),
|
|
('new-in-town', 'New in Town'),
|
|
]
|
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
|
professional_headline = models.CharField(max_length=255, blank=True)
|
|
transition_status = models.CharField(max_length=50, choices=TRANSITION_CHOICES, default='none')
|
|
bio = models.TextField(blank=True)
|
|
location_city = models.CharField(max_length=100, blank=True)
|
|
intents = models.ManyToManyField(Intent, blank=True)
|
|
value_tags = models.ManyToManyField(ValueTag, blank=True)
|
|
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)
|
|
|
|
# Gaming Fields
|
|
primary_game = models.ForeignKey(Game, on_delete=models.SET_NULL, null=True, blank=False, related_name='primary_players')
|
|
secondary_games = models.ManyToManyField(Game, blank=True, related_name='secondary_players')
|
|
platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES, blank=False)
|
|
gamer_tag = models.CharField(max_length=100, blank=False)
|
|
rank = models.CharField(max_length=100, blank=True)
|
|
preferred_role = models.CharField(max_length=100, blank=True)
|
|
|
|
# Momentum & Engagement
|
|
accountability_streak = models.IntegerField(default=0)
|
|
|
|
# Auth & Security
|
|
is_email_verified = models.BooleanField(default=False)
|
|
two_factor_enabled = models.BooleanField(default=False)
|
|
onboarding_completed = models.BooleanField(default=False)
|
|
|
|
# Multitenancy (Future Proofing)
|
|
organization_id = models.IntegerField(null=True, blank=True)
|
|
|
|
@property
|
|
def get_avatar_url(self):
|
|
if self.avatar and hasattr(self.avatar, 'url'):
|
|
return self.avatar.url
|
|
return f"https://i.pravatar.cc/150?u={self.user.username}"
|
|
|
|
@property
|
|
def connection_count(self):
|
|
return Match.objects.filter(models.Q(user_a=self.user) | models.Q(user_b=self.user)).count()
|
|
|
|
@property
|
|
def following_count(self):
|
|
return Follow.objects.filter(follower=self.user).count()
|
|
|
|
@property
|
|
def followers_count(self):
|
|
return Follow.objects.filter(followed=self.user).count()
|
|
|
|
@property
|
|
def events_attended_count(self):
|
|
return self.user.rsvps.filter(status='going').count()
|
|
|
|
@property
|
|
def profile_completion_percentage(self):
|
|
steps = 0
|
|
total_steps = 6
|
|
if self.gamer_tag: steps += 1
|
|
if self.bio: steps += 1
|
|
if self.location_city: steps += 1
|
|
if self.intents.exists(): steps += 1
|
|
if self.avatar: steps += 1
|
|
if self.primary_game: steps += 1
|
|
return int((steps / total_steps) * 100)
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username}'s Profile"
|
|
|
|
class Connection(models.Model):
|
|
user1 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='connections1')
|
|
user2 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='connections2')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
unique_together = ('user1', 'user2')
|
|
|
|
def __str__(self):
|
|
return f"{self.user1.username} <-> {self.user2.username}"
|
|
|
|
class Follow(models.Model):
|
|
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='following_relations')
|
|
followed = models.ForeignKey(User, on_delete=models.CASCADE, related_name='follower_relations')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
unique_together = ('follower', 'followed')
|
|
|
|
def __str__(self):
|
|
return f"{self.follower.username} follows {self.followed.username}"
|
|
|
|
class Group(models.Model):
|
|
name = models.CharField(max_length=255)
|
|
description = models.TextField()
|
|
is_private = models.BooleanField(default=False)
|
|
moderators = models.ManyToManyField(User, related_name='moderated_groups')
|
|
members = models.ManyToManyField(User, related_name='joined_groups')
|
|
organization_id = models.IntegerField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return str(self.name)
|
|
|
|
class EventTag(models.Model):
|
|
name = models.CharField(max_length=50, unique=True)
|
|
|
|
def __str__(self):
|
|
return str(self.name)
|
|
|
|
class Event(models.Model):
|
|
VISIBILITY_CHOICES = [
|
|
('public', 'Public (Members Only)'),
|
|
('group', 'Group Only'),
|
|
('invite', 'Invite Only'),
|
|
]
|
|
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_events')
|
|
title = models.CharField(max_length=255)
|
|
description = models.TextField()
|
|
start_datetime = models.DateTimeField()
|
|
end_datetime = models.DateTimeField(null=True, blank=True)
|
|
timezone = models.CharField(max_length=50, default='UTC')
|
|
location_name = models.CharField(max_length=255)
|
|
location_address = models.CharField(max_length=255, blank=True)
|
|
city = models.CharField(max_length=100, blank=True)
|
|
state = models.CharField(max_length=100, blank=True)
|
|
is_online = models.BooleanField(default=False)
|
|
online_url = models.URLField(blank=True)
|
|
visibility = models.CharField(max_length=20, choices=VISIBILITY_CHOICES, default='public')
|
|
group = models.ForeignKey(Group, on_delete=models.SET_NULL, null=True, blank=True, related_name='group_events')
|
|
capacity = models.PositiveIntegerField(null=True, blank=True)
|
|
cover_image = models.ImageField(upload_to='events/', blank=True, null=True)
|
|
tags = models.ManyToManyField(EventTag, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
|
updated_at = models.DateTimeField(auto_now=True, null=True)
|
|
|
|
def __str__(self):
|
|
return str(self.title)
|
|
|
|
@property
|
|
def rsvp_count(self):
|
|
return self.rsvps.filter(status='going').count()
|
|
|
|
class RSVP(models.Model):
|
|
STATUS_CHOICES = [
|
|
('going', 'Going'),
|
|
('maybe', 'Maybe'),
|
|
('not_going', 'Not Going'),
|
|
]
|
|
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='rsvps')
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='rsvps')
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = ('event', 'user')
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username} - {self.event.title} ({self.status})"
|
|
|
|
class EventInvite(models.Model):
|
|
STATUS_CHOICES = [
|
|
('pending', 'Pending'),
|
|
('accepted', 'Accepted'),
|
|
('declined', 'Declined'),
|
|
]
|
|
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='invites')
|
|
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='invites_sent')
|
|
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='invites_received')
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"Invite: {self.event.title} to {self.to_user.username}"
|
|
|
|
class Report(models.Model):
|
|
reporter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reports_made')
|
|
reported_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reports_received', null=True, blank=True)
|
|
content = models.TextField()
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
resolved = models.BooleanField(default=False)
|
|
organization_id = models.IntegerField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return f"Report by {self.reporter.username} at {self.timestamp}"
|
|
|
|
class Thread(models.Model):
|
|
participants = models.ManyToManyField(User, related_name='threads')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return f"Thread with {self.participants.count()} participants"
|
|
|
|
class Message(models.Model):
|
|
thread = models.ForeignKey(Thread, on_delete=models.CASCADE, related_name='messages', null=True, blank=True)
|
|
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages')
|
|
recipient = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_messages', null=True, blank=True)
|
|
body = models.TextField()
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
is_read = models.BooleanField(default=False)
|
|
|
|
class Meta:
|
|
ordering = ['timestamp']
|
|
|
|
def __str__(self):
|
|
return f"From {self.sender.username} at {self.timestamp}"
|
|
|
|
class Like(models.Model):
|
|
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='likes_given')
|
|
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='likes_received')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
unique_together = ('from_user', 'to_user')
|
|
|
|
class ConnectionRequest(models.Model):
|
|
STATUS_CHOICES = [
|
|
('pending', 'Pending'),
|
|
('accepted', 'Accepted'),
|
|
('declined', 'Declined'),
|
|
('canceled', 'Canceled'),
|
|
('blocked', 'Blocked'),
|
|
]
|
|
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='requests_sent')
|
|
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='requests_received')
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
responded_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
unique_together = ('from_user', 'to_user')
|
|
|
|
class Match(models.Model):
|
|
STATUS_CHOICES = [
|
|
('active', 'Active'),
|
|
('archived', 'Archived'),
|
|
]
|
|
user_a = models.ForeignKey(User, on_delete=models.CASCADE, related_name='matches_a')
|
|
user_b = models.ForeignKey(User, on_delete=models.CASCADE, related_name='matches_b')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
|
|
|
|
class Meta:
|
|
unique_together = ('user_a', 'user_b')
|
|
|
|
class Block(models.Model):
|
|
blocker = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blocks_given')
|
|
blocked = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blocks_received')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
unique_together = ('blocker', 'blocked')
|
|
|
|
class Post(models.Model):
|
|
POST_TYPE_CHOICES = [
|
|
('reflection', 'Reflection'),
|
|
('looking_for', 'Looking For'),
|
|
('offering', 'Offering'),
|
|
('event_invite', 'Event Invite'),
|
|
('progress_update', 'Progress Update'),
|
|
('skill_share', 'Skill Share'),
|
|
]
|
|
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
|
|
content = models.TextField()
|
|
image = models.ImageField(upload_to='posts/', blank=True, null=True)
|
|
post_type = models.CharField(max_length=20, choices=POST_TYPE_CHOICES, default='reflection')
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-timestamp']
|
|
|
|
def __str__(self):
|
|
return f"Post by {self.author.username} at {self.timestamp}"
|
|
|
|
def user_has_reacted(self, user, reaction_type='heart'):
|
|
if user.is_authenticated:
|
|
return self.reactions.filter(user=user, reaction_type=reaction_type).exists()
|
|
return False
|
|
|
|
class Comment(models.Model):
|
|
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
|
|
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
content = models.TextField()
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['timestamp']
|
|
|
|
def __str__(self):
|
|
return f"Comment by {self.author.username} on {self.post}"
|
|
|
|
class Reaction(models.Model):
|
|
REACTION_CHOICES = [
|
|
('heart', 'Heart'),
|
|
('like', 'Like'),
|
|
('smile', 'Smile'),
|
|
]
|
|
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='reactions')
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
reaction_type = models.CharField(max_length=20, choices=REACTION_CHOICES, default='heart')
|
|
|
|
class Meta:
|
|
unique_together = ('post', 'user', 'reaction_type')
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username} {self.reaction_type}ed {self.post}"
|
|
|
|
class HiddenPost(models.Model):
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='hidden_posts')
|
|
post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
unique_together = ('user', 'post')
|