diff --git a/assets/pasted-20260217-172745-5275e704.png b/assets/pasted-20260217-172745-5275e704.png new file mode 100644 index 0000000..bde165a Binary files /dev/null and b/assets/pasted-20260217-172745-5275e704.png differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index eaf2d70..c132140 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index e14328a..c1db59b 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 43d0c77..ae00898 100644 --- a/config/settings.py +++ b/config/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'core', + 'groups', ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index 1a48858..fec9503 100644 --- a/config/urls.py +++ b/config/urls.py @@ -21,6 +21,7 @@ from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), + path("groups/", include("groups.urls")), path("", include("core.urls")), ] diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..f897e2c Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index efbc4ce..66b8924 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 e5c2e74..5193af8 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 15ad71e..ce2c177 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..c5403e8 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,35 @@ +from django import forms +from .models import Event, EventTag + +class EventForm(forms.ModelForm): + class Meta: + model = Event + fields = [ + 'title', 'description', 'start_datetime', 'end_datetime', + 'timezone', 'location_name', 'location_address', 'city', 'state', + 'is_online', 'online_url', 'visibility', 'group', 'capacity', + 'cover_image', 'tags' + ] + labels = { + 'title': 'Session Title', + 'description': 'Session Description', + 'is_online': 'This is an online session', + 'group': 'Squad', + } + widgets = { + 'start_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}), + 'end_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}), + 'description': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}), + 'title': forms.TextInput(attrs={'class': 'form-control'}), + 'timezone': forms.TextInput(attrs={'class': 'form-control'}), + 'location_name': forms.TextInput(attrs={'class': 'form-control'}), + 'location_address': forms.TextInput(attrs={'class': 'form-control'}), + 'city': forms.TextInput(attrs={'class': 'form-control'}), + 'state': forms.TextInput(attrs={'class': 'form-control'}), + 'online_url': forms.URLInput(attrs={'class': 'form-control'}), + 'visibility': forms.Select(attrs={'class': 'form-select'}), + 'group': forms.Select(attrs={'class': 'form-select'}), + 'capacity': forms.NumberInput(attrs={'class': 'form-control'}), + 'cover_image': forms.FileInput(attrs={'class': 'form-control'}), + 'tags': forms.SelectMultiple(attrs={'class': 'form-select'}), + } diff --git a/core/migrations/0009_alter_message_recipient_thread_message_thread_block_and_more.py b/core/migrations/0009_alter_message_recipient_thread_message_thread_block_and_more.py new file mode 100644 index 0000000..dbbf3c6 --- /dev/null +++ b/core/migrations/0009_alter_message_recipient_thread_message_thread_block_and_more.py @@ -0,0 +1,86 @@ +# Generated by Django 5.2.7 on 2026-02-17 17:34 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_follow'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='message', + name='recipient', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Thread', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('participants', models.ManyToManyField(related_name='threads', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='message', + name='thread', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='core.thread'), + ), + migrations.CreateModel( + name='Block', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('blocked', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks_received', to=settings.AUTH_USER_MODEL)), + ('blocker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks_given', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('blocker', 'blocked')}, + }, + ), + migrations.CreateModel( + name='ConnectionRequest', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('declined', 'Declined'), ('canceled', 'Canceled'), ('blocked', 'Blocked')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('responded_at', models.DateTimeField(blank=True, null=True)), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests_sent', to=settings.AUTH_USER_MODEL)), + ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests_received', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('from_user', 'to_user')}, + }, + ), + migrations.CreateModel( + name='Like', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes_given', to=settings.AUTH_USER_MODEL)), + ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes_received', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('from_user', 'to_user')}, + }, + ), + migrations.CreateModel( + name='Match', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('status', models.CharField(choices=[('active', 'Active'), ('archived', 'Archived')], default='active', max_length=20)), + ('user_a', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matches_a', to=settings.AUTH_USER_MODEL)), + ('user_b', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matches_b', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user_a', 'user_b')}, + }, + ), + ] diff --git a/core/migrations/0010_eventtag_remove_event_attendees_and_more.py b/core/migrations/0010_eventtag_remove_event_attendees_and_more.py new file mode 100644 index 0000000..d56f27c --- /dev/null +++ b/core/migrations/0010_eventtag_remove_event_attendees_and_more.py @@ -0,0 +1,157 @@ +# Generated by Django 5.2.7 on 2026-02-17 17:48 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_alter_message_recipient_thread_message_thread_block_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='EventTag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, unique=True)), + ], + ), + migrations.RemoveField( + model_name='event', + name='attendees', + ), + migrations.RemoveField( + model_name='event', + name='end_time', + ), + migrations.RemoveField( + model_name='event', + name='location', + ), + migrations.RemoveField( + model_name='event', + name='organization_id', + ), + migrations.RemoveField( + model_name='event', + name='organizer', + ), + migrations.RemoveField( + model_name='event', + name='start_time', + ), + migrations.AddField( + model_name='event', + name='capacity', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='event', + name='city', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='event', + name='cover_image', + field=models.ImageField(blank=True, null=True, upload_to='events/'), + ), + migrations.AddField( + model_name='event', + name='created_at', + field=models.DateTimeField(auto_now_add=True, null=True), + ), + migrations.AddField( + model_name='event', + name='creator', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='event', + name='end_datetime', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='event', + name='is_online', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='event', + name='location_address', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='event', + name='location_name', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='event', + name='online_url', + field=models.URLField(blank=True), + ), + migrations.AddField( + model_name='event', + name='start_datetime', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='event', + name='state', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='event', + name='timezone', + field=models.CharField(default='UTC', max_length=50), + ), + migrations.AddField( + model_name='event', + name='updated_at', + field=models.DateTimeField(auto_now=True, null=True), + ), + migrations.AddField( + model_name='event', + name='visibility', + field=models.CharField(choices=[('public', 'Public (Members Only)'), ('group', 'Group Only'), ('invite', 'Invite Only')], default='public', max_length=20), + ), + migrations.AlterField( + model_name='event', + name='group', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='group_events', to='core.group'), + ), + migrations.CreateModel( + name='EventInvite', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('declined', 'Declined')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='core.event')), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites_sent', to=settings.AUTH_USER_MODEL)), + ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites_received', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='event', + name='tags', + field=models.ManyToManyField(blank=True, to='core.eventtag'), + ), + migrations.CreateModel( + name='RSVP', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('going', 'Going'), ('maybe', 'Maybe'), ('not_going', 'Not Going')], max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rsvps', to='core.event')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rsvps', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('event', 'user')}, + }, + ), + ] diff --git a/core/migrations/0011_alter_event_creator_alter_event_location_name_and_more.py b/core/migrations/0011_alter_event_creator_alter_event_location_name_and_more.py new file mode 100644 index 0000000..1337cf4 --- /dev/null +++ b/core/migrations/0011_alter_event_creator_alter_event_location_name_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.7 on 2026-02-17 17:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_eventtag_remove_event_attendees_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='creator', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='event', + name='location_name', + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name='event', + name='start_datetime', + field=models.DateTimeField(), + ), + ] diff --git a/core/migrations/0012_game_profile_gamer_tag_profile_platform_and_more.py b/core/migrations/0012_game_profile_gamer_tag_profile_platform_and_more.py new file mode 100644 index 0000000..5e4b3ec --- /dev/null +++ b/core/migrations/0012_game_profile_gamer_tag_profile_platform_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 5.2.7 on 2026-02-17 18:22 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_alter_event_creator_alter_event_location_name_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Game', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ('slug', models.SlugField(max_length=255, unique=True)), + ('genre', models.CharField(choices=[('fps', 'FPS'), ('moba', 'MOBA'), ('battle_royale', 'Battle Royale'), ('sports', 'Sports'), ('mmo', 'MMO'), ('fighting', 'Fighting'), ('rpg', 'RPG'), ('strategy', 'Strategy'), ('other', 'Other')], max_length=50)), + ('team_size', models.PositiveIntegerField(blank=True, null=True)), + ('has_roles', models.BooleanField(default=False)), + ('roles_json', models.JSONField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.AddField( + model_name='profile', + name='gamer_tag', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='profile', + name='platform', + field=models.CharField(blank=True, choices=[('pc', 'PC'), ('playstation', 'PlayStation'), ('xbox', 'Xbox'), ('nintendo', 'Nintendo Switch'), ('mobile', 'Mobile')], max_length=20), + ), + migrations.AddField( + model_name='profile', + name='preferred_role', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='profile', + name='rank', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='profile', + name='primary_game', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_players', to='core.game'), + ), + migrations.AddField( + model_name='profile', + name='secondary_games', + field=models.ManyToManyField(blank=True, related_name='secondary_players', to='core.game'), + ), + ] diff --git a/core/migrations/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.py b/core/migrations/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.py new file mode 100644 index 0000000..24afffb --- /dev/null +++ b/core/migrations/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.7 on 2026-02-17 18:35 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0012_game_profile_gamer_tag_profile_platform_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='gamer_tag', + field=models.CharField(max_length=100), + ), + migrations.AlterField( + model_name='profile', + name='platform', + field=models.CharField(choices=[('pc', 'PC'), ('playstation', 'PlayStation'), ('xbox', 'Xbox'), ('nintendo', 'Nintendo Switch'), ('mobile', 'Mobile')], max_length=20), + ), + migrations.AlterField( + model_name='profile', + name='primary_game', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_players', to='core.game'), + ), + ] diff --git a/core/migrations/__pycache__/0009_alter_message_recipient_thread_message_thread_block_and_more.cpython-311.pyc b/core/migrations/__pycache__/0009_alter_message_recipient_thread_message_thread_block_and_more.cpython-311.pyc new file mode 100644 index 0000000..acf75e2 Binary files /dev/null and b/core/migrations/__pycache__/0009_alter_message_recipient_thread_message_thread_block_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0010_eventtag_remove_event_attendees_and_more.cpython-311.pyc b/core/migrations/__pycache__/0010_eventtag_remove_event_attendees_and_more.cpython-311.pyc new file mode 100644 index 0000000..138b7be Binary files /dev/null and b/core/migrations/__pycache__/0010_eventtag_remove_event_attendees_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0011_alter_event_creator_alter_event_location_name_and_more.cpython-311.pyc b/core/migrations/__pycache__/0011_alter_event_creator_alter_event_location_name_and_more.cpython-311.pyc new file mode 100644 index 0000000..a908e29 Binary files /dev/null and b/core/migrations/__pycache__/0011_alter_event_creator_alter_event_location_name_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0012_game_profile_gamer_tag_profile_platform_and_more.cpython-311.pyc b/core/migrations/__pycache__/0012_game_profile_gamer_tag_profile_platform_and_more.cpython-311.pyc new file mode 100644 index 0000000..3b9b038 Binary files /dev/null and b/core/migrations/__pycache__/0012_game_profile_gamer_tag_profile_platform_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.cpython-311.pyc b/core/migrations/__pycache__/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.cpython-311.pyc new file mode 100644 index 0000000..2b8cea3 Binary files /dev/null and b/core/migrations/__pycache__/0013_alter_profile_gamer_tag_alter_profile_platform_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index a5db98c..b4007b3 100644 --- a/core/models.py +++ b/core/models.py @@ -14,7 +14,37 @@ class ValueTag(models.Model): 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'), @@ -32,6 +62,14 @@ class Profile(models.Model): 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) @@ -51,7 +89,7 @@ class Profile(models.Model): @property def connection_count(self): - return Connection.objects.filter(models.Q(user1=self.user) | models.Q(user2=self.user)).count() + return Match.objects.filter(models.Q(user_a=self.user) | models.Q(user_b=self.user)).count() @property def following_count(self): @@ -63,17 +101,18 @@ class Profile(models.Model): @property def events_attended_count(self): - return self.user.attending_events.count() + return self.user.rsvps.filter(status='going').count() @property def profile_completion_percentage(self): steps = 0 - total_steps = 5 - if self.professional_headline: steps += 1 + 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): @@ -112,20 +151,78 @@ class Group(models.Model): 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_time = models.DateTimeField() - end_time = models.DateTimeField() - location = models.CharField(max_length=255) - group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='events', null=True, blank=True) - organizer = models.ForeignKey(User, on_delete=models.CASCADE) - attendees = models.ManyToManyField(User, related_name='attending_events') - organization_id = models.IntegerField(null=True, blank=True) + 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) @@ -137,9 +234,18 @@ class Report(models.Model): 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') + 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) @@ -148,7 +254,53 @@ class Message(models.Model): ordering = ['timestamp'] def __str__(self): - return f"From {self.sender.username} to {self.recipient.username} at {self.timestamp}" + 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 = [ diff --git a/core/static/core/images/logo.png b/core/static/core/images/logo.png new file mode 100644 index 0000000..bde165a Binary files /dev/null and b/core/static/core/images/logo.png differ diff --git a/core/templates/base.html b/core/templates/base.html index 0856475..5e5d643 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -19,21 +19,33 @@
-