Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profilepage #88

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __pycache__
django.log
.env
.ruff_cache
cardie/media/profile_images
4 changes: 4 additions & 0 deletions cardie/authentication/admin.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
from django.contrib import admin
from .models import Profile

# Register your models here.

admin.site.register(Profile)
19 changes: 19 additions & 0 deletions cardie/authentication/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django import forms
from .models import Profile, User



class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email']


class ProfileUpdateForm(forms.ModelForm):
profile_image = forms.ImageField(
widget=forms.FileInput(attrs={'class': 'hidden'}),
required=False
)
class Meta:
model = Profile
fields = ['profile_image', 'bio']
24 changes: 24 additions & 0 deletions cardie/authentication/migrations/0004_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.0.3 on 2024-10-08 13:24

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authentication', '0003_user_wallet'),
]

operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile_image', models.ImageField(default='default_images/default_profile.jpg', upload_to='profile_images/')),
('bio', models.CharField(blank=True, max_length=256)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='authentication.user')),
],
),
]
12 changes: 11 additions & 1 deletion cardie/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,14 @@ class User(models.Model):
wallet = models.ManyToManyField("main.Card")

def __str__(self):
return(self.username)
return(self.username)


class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
profile_image = models.ImageField(upload_to='profile_images/',default='default_images/default_profile.jpg')
bio = models.CharField(max_length=256, blank=True)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"{self.user}'s profile image"
233 changes: 233 additions & 0 deletions cardie/authentication/templates/profile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
{% load static %}

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cardie | Profile</title>

<!-- External Scripts -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>

<!-- CSS Links -->
<link rel="stylesheet" type="text/css" href="{% static '/main/variables.css' %}">
<link rel="stylesheet" type="text/css" href="{% static '/main/ui.css' %}">
<link rel="stylesheet" type="text/css" href="{% static '/main/notifications.css' %}">
<link rel="stylesheet" type="text/css" href="{% static '/main/home.css' %}">
<link rel="stylesheet" type="text/css" href="{% static '/main/profile.css' %}">
<link rel="shortcut icon" type="image/png" href="{% static '/main/images/favicon_light.ico' %}"/>
</head>

<body>
<div id="home-top">
<a href="{% url 'index' %}">
<div id="home-top-logo">
<img id="home-top-image" src="{% static '/main/images/logo_light.png' %}">
</div>
</a>

<div id="home-top-profile" x-data="{ open: false }" @click.away="open = false">
<button id="home-top-username" class="ui_button_small" @click="open = !open">
{{ profile.user.username }} <i class="ph-bold ph-caret-down"></i>
</button>

<div id="home-top-profile-dropdown" x-show="open" x-transition>
<div class="dropdown-content">
<a href="{% url 'home' %}" >
<button class="ui_button_small view-profile-btn">
<i class="ph-bold ph-user"></i> Home
</button>
</a>
<button class="ui_button_small logout-btn" @click="log_out()">
<i class="ph-bold ph-sign-out"></i> Log Out
</button>
</div>
</div>
</div>
</div>

<form method="POST" enctype="multipart/form-data" x-ref="profileForm">
{% csrf_token %}
<div id="profile-container" x-data="{
editMode: false,
toggleEdit() {
this.editMode = !this.editMode;
},
triggerFileInput() {
this.$refs.fileInput.click();
},
handleFileChange(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
document.querySelector('.profile-image').src = e.target.result;
};
reader.readAsDataURL(file);
}
}
}">
<div id="profile-details">
<div class="detail-card">
<h2 class="ui_text_subheader_left">Account Details</h2>
<div class="info-item ui_text_body">
<i class="ph-bold ph-user"></i>
<template x-if="!editMode">
<span>{{ profile.user.username }}</span>
</template>
<template x-if="editMode">
{{ u_form.username }}
</template>
</div>
<div class="info-item ui_text_body">
<i class="ph-bold ph-envelope"></i>
<template x-if="!editMode">
<span>{{ profile.user.email }}</span>
</template>
<template x-if="editMode">
{{ u_form.email }}
</template>
</div>
<div class="info-item ui_text_body">
<i class="ph-bold ph-file-text"></i>
<template x-if="!editMode">
{% if profile.bio %}

<span>{{ profile.bio }}</span>
{% else %}
<p class="ui_text_body">No bio available.</p>
{% endif %}
</template>
<template x-if="editMode">
{{ p_form.bio }}
</template>
</div>
<div class="info-item ui_text_body">
<i class="ph-bold ph-calendar"></i>
<span>Created: {{ profile.created_at }}</span>
</div>
</div>

<div class="detail-card">
<h2 class="ui_text_subheader_left">Account Actions</h2>
<div class="action-buttons">
<template x-if="!editMode">
<button type="button" class="ui_button_grid" @click="toggleEdit()">
<i class="ph-bold ph-pencil"></i>
<span>Edit Profile</span>
</button>
</template>
<template x-if="editMode">
<button type="submit" class="ui_button_grid" @click="$refs.profileForm.submit()">
<i class="ph-bold ph-check"></i>
<span>Save Changes</span>
</button>
</template>
<template x-if="editMode">
<button type="button" class="ui_button_grid" @click="toggleEdit()">
<i class="ph-bold ph-x"></i>
<span>Cancel</span>
</button>
</template>
<button type="button" class="ui_button_grid" @click="openSettingsModal()">
<i class="ph-bold ph-gear"></i>
<span>Settings</span>
</button>
</div>
</div>
</div>

<div id="profile-image-section">
<div class="profile-image-container" @click="editMode && triggerFileInput()">
<img class="profile-image" src="{{ profile.profile_image.url }}" alt="Profile Image">
<template x-if="editMode">
<div class="profile-image-overlay">
<i class="ph-bold ph-camera" style="font-size: 24px;"></i>
</div>
</template>
</div>
<input
type="file"
name="profile_image"
x-ref="fileInput"
@change="handleFileChange"
accept="image/*"
style="display: none;">
</div>
</div>
</form>

<!-- Settings Modal -->
<div class="modal-backdrop" id="settingsModal" style="display: none;">
<div class="settings-modal">
<div class="modal-sidebar">
<div class="sidebar-item active" data-section="general">
<i class="ph-bold ph-gear"></i>
General
</div>
<div class="sidebar-item" data-section="security">
<i class="ph-bold ph-shield"></i>
Security
</div>
<div class="sidebar-item" data-section="notifications">
<i class="ph-bold ph-bell"></i>
Notifications
</div>
</div>
<div class="modal-content">
<div class="modal-header">
<h2 class="ui_text_subheader_left">Settings</h2>
<button class="close-button" onclick="closeSettingsModal()">&times;</button>
</div>

<div class="content-section active" id="general">
<h3 class="ui_text_smallersubheader">General Settings</h3>
<!-- Add general settings content here -->
</div>

<div class="content-section" id="security">
<h3 class="ui_text_smallersubheader">Security Settings</h3>
<div class="form-group">
<button class="ui_button_small" onclick="togglePasswordFields()">Change Password</button>
<div id="passwordFields" style="display: none; margin-top: 20px;">
<div class="form-group">
<label class="ui_text_body" for="currentPassword">Current Password</label>
<input class="ui_input_generic" type="password" id="currentPassword">
</div>
<div class="form-group">
<label class="ui_text_body" for="newPassword">New Password</label>
<input class="ui_input_generic" type="password" id="newPassword">
</div>
<div class="form-group">
<label class="ui_text_body" for="confirmPassword">Confirm New Password</label>
<input class="ui_input_generic" type="password" id="confirmPassword">
</div>
<button class="ui_button_small" onclick="updatePassword()">Update Password</button>
</div>
</div>
</div>

<div class="content-section" id="notifications">
<h3 class="ui_text_smallersubheader">Notification Settings</h3>
<!-- Add notification settings content here -->
</div>
</div>
</div>
</div>

<script>
var server_ip = "{{ server_ip }}";
var production = "{{ production }}";
var username = "{{ username }}"
</script>

<!-- Script tags -->
<script src="{% static '/main/scripts/profile/profile.js' %}"></script>
<script src="{% static '/main/scripts/home/home.js' %}"></script>
<script src="{% static '/main/scripts/global/favicon.js' %}"></script>
<script src="{% static '/main/scripts/global/notifications.js' %}"></script>
<script src="{% static '/main/scripts/global/logging.js' %}"></script>
<script src="{% static '/main/scripts/global/background_blur.js' %}"></script>
</body>
</html>
56 changes: 54 additions & 2 deletions cardie/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from django.shortcuts import HttpResponse
from django.contrib.auth.hashers import make_password, check_password

from authentication.models import User
# from authentication.models import User
from main.models import Server, Card, TempCard
from main import views

from django.utils import timezone
import uuid


from .forms import UserUpdateForm, ProfileUpdateForm
from django.shortcuts import render, get_object_or_404, redirect
from .models import Profile
from django.contrib import messages
from .models import User

def sign_in(request):
server = Server.objects.all()[0] # TODO: What if there is multiple server objects?

Expand Down Expand Up @@ -102,6 +109,8 @@ def create_account(request):
user = User(username=username, password=hashed_password, email=email, date_created=timezone.now())
user.save()

Profile.objects.create(user=user)

request.session["username"] = username
request.session["password"] = password

Expand All @@ -110,4 +119,47 @@ def create_account(request):
else:
return HttpResponse("error_missing_headers")
else:
return HttpResponse("error_create_account_disabled")
return HttpResponse("error_create_account_disabled")


def profile(request):
username = request.session.get("username")
user = get_object_or_404(User, username=username)
profile, created = Profile.objects.get_or_create(user=user)

server_info = Server.objects.all()[0]
is_editing = request.GET.get('edit') == 'true'

if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=user)
p_form = ProfileUpdateForm(request.POST, request.FILES, instance=profile)

if u_form.is_valid() and p_form.is_valid():
new_username = u_form.cleaned_data.get('username')
user = u_form.save()
p_form.save()

# Update the session if username has changed
if new_username and new_username != username:
request.session['username'] = new_username

messages.success(request, 'Your profile has been updated!')
return redirect('profile')
else:
messages.error(request, 'Please correct the errors below.')
else:
u_form = UserUpdateForm(instance=user)
p_form = ProfileUpdateForm(instance=profile)

server_info = Server.objects.all()[0]

context = {
'profile': profile,
'u_form': u_form,
'p_form': p_form,
'is_editing': is_editing,
"server_ip": server_info.ip,
"production": server_info.production,
}

return render(request, 'profile.html', context)
Loading
Loading