Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added my_website/accounts/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions my_website/accounts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin
from .managers import UserManager
from .models import User

# Register your models here.
admin.site.register(User)
6 changes: 6 additions & 0 deletions my_website/accounts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'
16 changes: 16 additions & 0 deletions my_website/accounts/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):
def create_user(self, email, name, password=None, **extra_fields):
if not email:
raise ValueError("Users must have a valid email adress")
email = self.normalize_email(email)
user = self.model(email=email, name=name, **extra_fields)
user.set_unusable_password()
user.save()
return user

def create_superuser(self, email, name, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
return self.create_user(email, name, **extra_fields)
36 changes: 36 additions & 0 deletions my_website/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.11 on 2025-11-19 21:32

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('email', models.EmailField(max_length=254, unique=True)),
('name', models.CharField(max_length=255)),
('is_verified', models.BooleanField(default=False)),
('mailing_list', models.BooleanField(default=False)),
('is_staff', models.BooleanField(default=False)),
('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'abstract': False,
},
),
]
Empty file.
21 changes: 21 additions & 0 deletions my_website/accounts/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from .managers import UserManager

# Create your models here.
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
name = models.CharField(max_length=255)
is_verified = models.BooleanField(default=False)
mailing_list = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(default=timezone.now)

USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["name"]

objects = UserManager()

def __str__(self):
return self.email
12 changes: 12 additions & 0 deletions my_website/accounts/templates/accounts/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "core/layout.html" %}
{% block title %}
Login
{% endblock %}
{% block body %}
<h1>Login</h1>
<form action="{% url 'accounts:login' %}" method="post">
{% csrf_token %}
<input type="email" name="email" placeholder="[email protected]" required autofocus>
<button type="submit">Login</button>
</form>
{% endblock %}
13 changes: 13 additions & 0 deletions my_website/accounts/templates/accounts/register.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "core/layout.html" %}
{% block title %}
Register
{% endblock %}
{% block body %}
<h1>Register</h1>
<form action="{% url 'accounts:register' %}" method="post">
{% csrf_token %}
<input type="email" name="email" placeholder="[email protected]" required autofocus>
<input type="text" name="name" placeholder="Name" required>
<button type="submit">Register</button>
</form>
{% endblock %}
3 changes: 3 additions & 0 deletions my_website/accounts/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
29 changes: 29 additions & 0 deletions my_website/accounts/tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.core import signing
import time

def generate_login_token(data: dict, expires=1800):
data['exp'] = time.time() + expires
return signing.dumps(data, salt="magic-link")

def generate_verification_token(data: dict, expires=60 * 60 * 24):
data['exp'] = time.time() + expires
return signing.dumps(data, salt="magic-link")


def verify_login_token(token, max_age=1800):
try:
data = signing.loads(token, salt="magic-link", max_age=max_age)
if data.get('exp', 0) < time.time():
return None
return data
except Exception:
return None

def verify_verification_token(token, max_age=60*60*24):
try:
data = signing.loads(token, salt="magic-link", max_age=max_age)
if data.get('exp', 0) < time.time():
return None
return data
except Exception:
return None
13 changes: 13 additions & 0 deletions my_website/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
path('register/', views.register, name='register'),
path('login/', views.login_request, name='login'),
path('verify/', views.verify_email, name='verify'),
path('login/confirm/', views.login_confirm, name='login_confirm'),
path('logout/', views.logout_view, name='logout'),
path('delete/<str:email>/', views.delete_user, name='delete_user')
]
130 changes: 130 additions & 0 deletions my_website/accounts/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from django.shortcuts import render, redirect, HttpResponse, HttpResponseRedirect
from django.contrib.auth import login, logout, get_user_model
from django.urls import reverse
from django.core.mail import send_mail
from django.contrib import messages
from .tokens import generate_login_token, generate_verification_token, verify_login_token, verify_verification_token
from django.contrib.admin.views.decorators import staff_member_required
from dotenv import load_dotenv
import os

load_dotenv()

# Host Email
sender = os.getenv("EMAIL_HOST_USER")

User = get_user_model()

# Create your views here.
def register(request):
if request.method == "GET":
return render(request, "accounts/register.html")
# POST
email = request.POST.get("email")
name = request.POST.get("name")
# Gets whether the User want to be part of the mailing list
mailing_list = request.POST.get("mailing_list")
if mailing_list == None:
mailing_list = False

if User.objects.filter(email=email).exists():
if User.objects.get(email=email).is_verified != True:
token = generate_verification_token({"email": email})
url = request.build_absolute_uri(reverse("accounts:verify") + f"?token={token}")
send_mail(
"Verify your account",
f"Click to verify your email:\n\n{url}",
sender,
[email],
)
return HttpResponse("Email already exisits, Check your email to verify")

messages.error(request, "Email already registered")
return redirect("accounts:register")

user = User.objects.create_user(
email=email,
name=name,
mailing_list=mailing_list
)

# Send the Verification Email Might extract into a seperate Helper method
token = generate_verification_token({"email": email})
url = request.build_absolute_uri(reverse("accounts:verify") + f"?token={token}")
send_mail(
"Verify your account",
f"Click to verify your email:\n\n{url}",
sender,
[email],
fail_silently=False
)
return HttpResponse("Check your email")

def login_request(request):
if request.method == "GET":
return render(request, "accounts/login.html")
# POST
email = request.POST.get("email")
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
messages.error(request, "Account Doesn't exist")
return redirect("accounts:login")

if not user.is_verified:
messages.error("Please verify your email address")
return redirect("accounts:login")
token = generate_login_token({"email": email})
url = request.build_absolute_uri(reverse("accounts:login_confirm") + f"?token={token}")
send_mail(
"Your login link",
f"Click here to log in:\n\n{url}",
sender,
[email],
fail_silently=False
)
return HttpResponse("login success, Check your Email")

def verify_email(request):
token = request.GET.get("token")
data = verify_verification_token(token)

if not data:
return HttpResponse("invalid token")

user = User.objects.get(email=data["email"])
user.is_verified = True
user.save()

return redirect("core:landing")

def login_confirm(request):
token = request.GET.get("token")
data = verify_login_token(token)

if not data:
return render(request, "accounts/invalid_token.html")
user = User.objects.get(email=data["email"])
login(request, user)
return redirect("core:landing")

def logout_view(request):
logout(request)
return redirect("core:landing")

# Endpoint to delete users
@staff_member_required
def delete_user(request, email):
try:
user = User.objects.get(email=email)
user.delete()
messages.success(request, "User deleted")

except User.DoesNotExist:
messages.error(request, "User dosn't exist")
return render(request, 'core:landing')

except Exception as e:
return render(request, 'core:landing',{'error':e.message})

return render(request, 'core:landing')
Empty file added my_website/core/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions my_website/core/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions my_website/core/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "core"
Empty file.
3 changes: 3 additions & 0 deletions my_website/core/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
7 changes: 7 additions & 0 deletions my_website/core/templates/core/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends 'core/layout.html' %}
{% block title %}
About
{% endblock %}
{% block body %}
<h1>About</h1>
{% endblock %}
76 changes: 76 additions & 0 deletions my_website/core/templates/core/landing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% extends "core/layout.html" %}
{% block title %}
Welcome!
{% endblock %}
{% block body %}
<h1>Welcome!</h1>
<div>
<a href="{% url 'core:about' %}"><h2>About</h2></a>
<p>I'm Mohamed, a computer science student currently studing in the University
of El Neelain in Sudan, I have a passion for Building things and tackling problems.
some of my projects include this website, A RAG chatbot and much more.
You can check my projects
<a href="{% url 'core:projects' %}">here</a>.
</p>
</div>

<div>
<a href="{% url 'accounts:register' %}"><h2>Register</h2></a>
<p>
Register to Enter my mailing list and get updates about my Projects and offers, it's
quick and easy I promise.
</p>
<p>Note: By default new accounts aren't added to the mailing list unless they consent to it.</p>
<form action="{% url 'accounts:register' %}" method="post">
{% csrf_token %}
<input type="email" name="email" placeholder="[email protected]">
<input type="text" name="name" placeholder="Name">
<input type="submit" value="Register">
</form>
</div>
<div>
<!--
TODO: div for login toggle between login and register using a button
-->
<a href="{% url 'accounts:login' %}"><h2>Login</h2></a>
<p>
Already have an account? login using your Email adress.
</p>
<form action="{% url 'accounts:login' %}", method="post">
{% csrf_token %}
<input type="email" name="email" placeholder="[email protected]">
<input type="submit" value="Login">
</form>
</div>
<!--
TODO: div should handle showcasing 2 or 3 nice comments from people who checked the website
-->
<div>
<ul>
<li>TODO: Nice comment #1</li>
<li>TODO: Nice comment #2</li>
<li>TODO: Nice comment #3</li>
</ul>
</div>
<!--
TODO: div should handle showcasing A couple selected projects from the projects route
-->
<div>
<a href="{% url 'core:projects' %}"><h2>Projects</h2></a>
<p>
Some of the projects i've worked on, if you like what you see be sure to leave a nice comment
and a rating, you can find more in the Projects Section.
</p>
<ul>
<li>TODO: project #1</li>
<li>TODO: project #2</li>
<li>TODO: project #3</li>
</ul>
</div>
<!--
TODO: div should handle the footer
-->
<div>
<h2>Footer</h2>
</div>
{% endblock %}
Loading
Loading