diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 881731c..4495897 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 42d995d..3ad7955 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 291d043..5e7d49d 100644 --- a/config/settings.py +++ b/config/settings.py @@ -180,3 +180,7 @@ if EMAIL_USE_SSL: # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + diff --git a/config/urls.py b/config/urls.py index bcfc074..069ee99 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,19 +1,3 @@ -""" -URL configuration for config project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin from django.urls import include, path from django.conf import settings @@ -27,3 +11,4 @@ urlpatterns = [ if settings.DEBUG: urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 18a063c..bd9adb9 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 ebb8c6e..5ed621c 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 8d204fa..3e91e5a 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..794d8bc --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2.7 on 2026-02-28 04:39 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('course', models.CharField(blank=True, max_length=255)), + ('year', models.CharField(blank=True, choices=[('Year 1', 'Year 1'), ('Year 2', 'Year 2'), ('Year 3', 'Year 3'), ('Year 4+', 'Year 4+'), ('Postgraduate', 'Postgraduate')], max_length=50)), + ('location', models.CharField(blank=True, choices=[('Canley', 'Canley'), ('Leamington Spa', 'Leamington Spa'), ('On-Campus', 'On-Campus'), ('Coventry', 'Coventry'), ('Other', 'Other')], max_length=50)), + ('looking_for', models.CharField(blank=True, choices=[('Friends', 'Friends'), ('Short-term', 'Short-term'), ('Long-term', 'Long-term')], max_length=50)), + ('bio', models.TextField(blank=True, max_length=500)), + ('is_subscribed', models.BooleanField(default=False)), + ('last_online', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Photo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(upload_to='profile_photos/')), + ('is_primary', models.BooleanField(default=False)), + ('uploaded_at', models.DateTimeField(auto_now_add=True)), + ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='core.profile')), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..ef1a3a0 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..035bc75 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,62 @@ from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. +class Profile(models.Model): + LOCATION_CHOICES = [ + ('Canley', 'Canley'), + ('Leamington Spa', 'Leamington Spa'), + ('On-Campus', 'On-Campus'), + ('Coventry', 'Coventry'), + ('Other', 'Other'), + ] + LOOKING_FOR_CHOICES = [ + ('Friends', 'Friends'), + ('Short-term', 'Short-term'), + ('Long-term', 'Long-term'), + ] + YEAR_CHOICES = [ + ('Year 1', 'Year 1'), + ('Year 2', 'Year 2'), + ('Year 3', 'Year 3'), + ('Year 4+', 'Year 4+'), + ('Postgraduate', 'Postgraduate'), + ] + + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') + course = models.CharField(max_length=255, blank=True) + year = models.CharField(max_length=50, choices=YEAR_CHOICES, blank=True) + location = models.CharField(max_length=50, choices=LOCATION_CHOICES, blank=True) + looking_for = models.CharField(max_length=50, choices=LOOKING_FOR_CHOICES, blank=True) + bio = models.TextField(max_length=500, blank=True) + is_subscribed = models.BooleanField(default=False) + last_online = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.user.username}'s profile" + + @property + def primary_photo(self): + primary = self.photos.filter(is_primary=True).first() + if primary: + return primary + return self.photos.first() + +class Photo(models.Model): + profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='photos') + image = models.ImageField(upload_to='profile_photos/') + is_primary = models.BooleanField(default=False) + uploaded_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Photo for {self.profile.user.username}" + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..140d13b 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,131 @@ - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Warwick Connect{% endblock %} + + + + + + + + + + {% load static %} + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} - + - +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + + {% block content %}{% endblock %} +
+ + + {% block extra_js %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/dashboard.html b/core/templates/core/dashboard.html new file mode 100644 index 0000000..24f833b --- /dev/null +++ b/core/templates/core/dashboard.html @@ -0,0 +1,211 @@ +{% extends "base.html" %} + +{% block title %}Discover Students | Warwick Connect{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Who's Online

+

Find fellow students at Warwick university.

+
+ +
+ {% for profile in visible_profiles %} +
+
+ {% if profile.primary_photo %} + {{ profile.user.username }} + {% else %} +
{{ profile.user.username|slice:":1"|upper }}
+ {% endif %} +
+
+
+
{{ profile.user.first_name|default:profile.user.username }}
+
{{ profile.course|default:"Student" }} · {{ profile.year }}
+
+ {{ profile.location }} + Looking for: {{ profile.looking_for }} +
+
+
+ {% empty %} +
+

No students found yet. Be the first to join!

+
+ {% endfor %} + + {% for profile in locked_profiles %} +
+
+
{{ profile.user.username|slice:":1"|upper }}
+
+
+
+
Unlock Full Grid
+
£3.99/mo
+ +

See all students online

+
+
+
+
Student Name
+
Course · Year
+
+
+ {% endfor %} +
+ +{% if not is_subscribed and locked_profiles %} +
+

Ready to see everyone?

+

Join other subscribers and unlock the full discovery grid, messaging, and more.

+ +
+{% endif %} +{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..a7bc3d4 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,128 @@ {% extends "base.html" %} -{% block title %}{{ project_name }}{% endblock %} +{% block title %}Connect with Warwick Students | Warwick Connect{% endblock %} -{% block head %} - - - +{% block extra_css %} {% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
+ Exclusive for @warwick.ac.uk students +

Discover your next connection at Warwick.

+

The only dating and networking platform dedicated exclusively to University of Warwick students. Find friends, study buddies, or something more.

+ +
+
+ +
+
+
+
+
+
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- -{% endblock %} \ No newline at end of file + + +
+
+
+
+
🎓
+

Verified Students Only

+

Every user is verified with a Warwick email address, ensuring a safe and authentic community.

+
+
+
+
+
📍
+

Nearby Connections

+

Whether you're in Leamington, Canley, or On-Campus, find students exactly where you are.

+
+
+
+
+
+

Meaningful Matches

+

Search by course, year, and what you're looking for to find the perfect match.

+
+
+
+
+{% endblock %} diff --git a/core/templates/core/login.html b/core/templates/core/login.html new file mode 100644 index 0000000..ad19391 --- /dev/null +++ b/core/templates/core/login.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block title %}Log In | Warwick Connect{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Log In

+

Welcome back, student.

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.errors %} +
+ {{ field.errors.0 }} +
+ {% endif %} +
+ {% endfor %} + + +
+ +

+ New to Warwick Connect? Join Now +

+
+{% endblock %} diff --git a/core/templates/core/profile_edit.html b/core/templates/core/profile_edit.html new file mode 100644 index 0000000..4db566a --- /dev/null +++ b/core/templates/core/profile_edit.html @@ -0,0 +1,108 @@ +{% extends "base.html" %} + +{% block title %}Complete Your Profile | Warwick Connect{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Complete Your Profile

+

Tell other students about yourself to find better connections.

+ +
+ {% csrf_token %} + +
+
+

Academic & Location

+
+ +
+ + {{ form.course }} +
+ +
+ + {{ form.year }} +
+ +
+ + {{ form.location }} +
+ +
+ + {{ form.looking_for }} +
+ +
+

About You

+
+ +
+ + {{ form.bio }} +
+
+ + +
+
+{% endblock %} diff --git a/core/templates/core/register.html b/core/templates/core/register.html new file mode 100644 index 0000000..5a2ce29 --- /dev/null +++ b/core/templates/core/register.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} + +{% block title %}Join Warwick Connect{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+

Create Account

+

Join the exclusive Warwick student network.

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {{ field.errors.0 }} +
+ {% endif %} +
+ {% endfor %} + + +
+ +

+ Already have an account? Log In +

+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..4f9e9c6 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,11 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), -] + path('', views.home, name='home'), + path('register/', views.register, name='register'), + path('login/', views.login_view, name='login'), + path('logout/', views.logout_view, name='logout'), + path('dashboard/', views.dashboard, name='dashboard'), + path('profile/edit/', views.profile_edit, name='profile_edit'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..e1e6277 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,110 @@ import os -import platform - +import random from django import get_version as django_version -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth import login, authenticate, logout +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django import forms +from .models import Profile, Photo from django.utils import timezone +from django.contrib.auth.models import User +class WarwickRegistrationForm(UserCreationForm): + email = forms.EmailField(required=True, help_text="Enter your @warwick.ac.uk email address") + + def clean_email(self): + email = self.cleaned_data.get('email') + if not email.endswith("@warwick.ac.uk"): + raise forms.ValidationError("You must use a @warwick.ac.uk email address to register.") + if User.objects.filter(email=email).exists(): + raise forms.ValidationError("This email is already in use.") + return email + + def save(self, commit=True): + user = super().save(commit=False) + user.email = self.cleaned_data['email'] + if commit: + user.save() + return user + +class ProfileEditForm(forms.ModelForm): + class Meta: + model = Profile + fields = ['course', 'year', 'location', 'looking_for', 'bio'] + widgets = { + 'course': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g. Computer Science'}), + 'year': forms.Select(attrs={'class': 'form-select'}), + 'location': forms.Select(attrs={'class': 'form-select'}), + 'looking_for': forms.Select(attrs={'class': 'form-select'}), + 'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Tell others a bit about yourself...'}), + } def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + if request.user.is_authenticated: + return redirect('dashboard') + return render(request, "core/index.html") + +def register(request): + if request.method == 'POST': + form = WarwickRegistrationForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + messages.success(request, "Welcome to Warwick Connect! Please complete your profile.") + return redirect('profile_edit') + else: + form = WarwickRegistrationForm() + return render(request, 'core/register.html', {'form': form}) + +@login_required +def profile_edit(request): + profile = request.user.profile + if request.method == 'POST': + form = ProfileEditForm(request.POST, instance=profile) + if form.is_valid(): + form.save() + messages.success(request, "Profile updated successfully!") + return redirect('dashboard') + else: + form = ProfileEditForm(instance=profile) + return render(request, 'core/profile_edit.html', {'form': form, 'profile': profile}) + +@login_required +def dashboard(request): + all_profiles = Profile.objects.exclude(user=request.user) + + if not request.user.profile.is_subscribed: + profiles_list = list(all_profiles) + if len(profiles_list) > 12: + visible_profiles = random.sample(profiles_list, 12) + locked_profiles = [p for p in profiles_list if p not in visible_profiles] + else: + visible_profiles = profiles_list + locked_profiles = [] + else: + visible_profiles = all_profiles + locked_profiles = [] context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + 'visible_profiles': visible_profiles, + 'locked_profiles': locked_profiles, + 'is_subscribed': request.user.profile.is_subscribed, } - return render(request, "core/index.html", context) + return render(request, 'core/dashboard.html', context) + +def login_view(request): + if request.method == 'POST': + form = AuthenticationForm(request, data=request.POST) + if form.is_valid(): + user = form.get_user() + login(request, user) + return redirect('dashboard') + else: + form = AuthenticationForm() + return render(request, 'core/login.html', {'form': form}) + +def logout_view(request): + logout(request) + return redirect('home')