First commit

This commit is contained in:
Andros Fenollosa
2022-04-19 21:14:01 +02:00
parent 8a9aebdf89
commit 12ba5461fd
37 changed files with 3082 additions and 0 deletions

View File

116
app/app_template/actions.py Normal file
View File

@ -0,0 +1,116 @@
from .forms import LoginForm, SignupForm
from asgiref.sync import async_to_sync
from django.template.loader import render_to_string
from django.urls import reverse
from channels.auth import login, logout
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from datetime import datetime
def send_page(self, page):
"""Render HTML and send page to client"""
# Prepare context data for page
context = {}
match page:
case "login":
context = {"form": LoginForm()}
case "signup":
context = {"form": SignupForm()}
# Add user to context if logged in
if "user" in self.scope:
context.update({ "user": self.scope["user"]})
context.update({"active_nav": page})
# Render HTML nav and send to client
self.send_html({
"selector": "#nav",
"html": render_to_string("components/_nav.html", context),
})
# Render HTML page and send to client
self.send_html({
"selector": "#main",
"html": render_to_string(f"pages/{page}.html", context),
"url": reverse(page),
})
# Hidrate page
match page:
case "home":
update_TODO(self)
def action_signup(self, data):
"""Sign up user"""
form = SignupForm(data)
user_exist = User.objects.filter(email=data["email"]).exists()
if form.is_valid() and data["password"] == data["password_confirm"] and not user_exist:
# Create user
user = User.objects.create_user(data["username"], data["email"], data["password"])
user.is_active = True
user.save()
# Login user
send_page(self, "login")
else:
# Send form errors
self.send_html({
"selector": "#main",
"html": render_to_string("pages/signup.html", {"form": form, "user_exist": user_exist, "passwords_do_not_match": data["password"] != data["password_confirm"]}),
"append": False,
"url": reverse("signup")
})
def action_login(self, data):
"""Log in user"""
form = LoginForm(data)
user = authenticate(username=data["email"], password=data["password"])
if form.is_valid() and user:
async_to_sync(login)(self.scope, user)
self.scope["session"].save()
send_page(self, "profile")
else:
self.send_html({
"selector": "#main",
"html": render_to_string("pages/login.html", {"form": form, "user_does_not_exist": user is None}),
"append": False,
"url": reverse("login")
})
def action_logout(self):
"""Log out user"""
async_to_sync(logout)(self.scope)
self.scope["session"].save()
send_page(self, "login")
def add_lap(self):
"""Add lap to Home page"""
# Send current time to client
self.send_html({
"selector": "#laps",
"html": render_to_string("components/_lap.html", {"time": datetime.now()}),
"append": True,
})
def add_task(self, data):
"""Add task from TODO section"""
# Add task to list
self.scope["session"]["tasks"].append(data["task"])
self.scope["session"].save()
# Update task list
update_TODO(self)
def update_TODO(self):
"""Update TODO list"""
self.send_html({
"selector": "#todo",
"html": render_to_string("components/_tasks.html", {"tasks": self.scope["session"]["tasks"]}),
"append": False,
})

6
app/app_template/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class SimpleAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "app.app_template"

View File

@ -0,0 +1,35 @@
from django.contrib.auth.models import User
from django.contrib.auth.backends import BaseBackend
class EmailBackend(BaseBackend):
"""
Email authentication backend
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
Authenticate a user based on email address as the user name.
"""
if "@" in username:
kwargs = {"email": username}
else:
kwargs = {"username": username}
try:
user = User.objects.get(**kwargs)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
# Future re-implementation with token for auto login
class TokenBackend(BaseBackend):
def authenticate(self, request, token=None):
pass

View File

@ -0,0 +1,55 @@
# app/app_template/consumers.py
from channels.generic.websocket import JsonWebsocketConsumer
import app.app_template.actions as actions
class ExampleConsumer(JsonWebsocketConsumer):
def connect(self):
"""Event when client connects"""
# Accept the connection
self.accept()
# Make session task list
if "tasks" not in self.scope["session"]:
self.scope["session"]["tasks"] = []
self.scope["session"].save()
def disconnect(self, close_code):
"""Event when client disconnects"""
pass
def receive_json(self, data_received):
"""
Event when data is received
All information will arrive in 2 variables:
"action", with the action to be taken
"data" with the information
"""
# Get the data
data = data_received["data"]
# Depending on the action we will do one task or another.
match data_received["action"]:
case "Change page":
actions.send_page(self, data["page"])
case "Signup":
actions.action_signup(self, data)
case "Login":
actions.action_login(self, data)
case "Logout":
actions.action_logout(self)
case "Add lap":
actions.add_lap(self)
case "Add task":
actions.add_task(self, data)
def send_html(self, event):
"""Event: Send html to client"""
data = {
"selector": event["selector"],
"html": event["html"],
"append": "append" in event and event["append"],
"url": event["url"] if "url" in event else "",
}
self.send_json(data)

39
app/app_template/forms.py Normal file
View File

@ -0,0 +1,39 @@
from django import forms
class LoginForm(forms.Form):
email = forms.CharField(
label="Email",
max_length=255,
widget=forms.EmailInput(attrs={"id": "login-email", "class": "input"}),
)
password = forms.CharField(
label="Password",
max_length=255,
widget=forms.PasswordInput(attrs={"id": "login-password", "class": "input"}),
)
class SignupForm(forms.Form):
username = forms.CharField(
label="Username",
max_length=255,
widget=forms.TextInput(attrs={"id": "signup-username", "class": "input"}),
)
email = forms.EmailField(
label="Email",
max_length=255,
widget=forms.EmailInput(attrs={"id": "signup-email", "class": "input"}),
)
password = forms.CharField(
label="Password",
max_length=255,
widget=forms.PasswordInput(attrs={"id": "signup-password", "class": "input"}),
)
password_confirm = forms.CharField(
label="Confirm Password",
max_length=255,
widget=forms.PasswordInput(
attrs={"id": "signup-password-confirm", "class": "input"}
),
)

View File

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,23 @@
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Example website</title>
<link rel="stylesheet" href="{% static 'css/main.css' %}">
<script defer src="{% static 'js/index.js' %}"></script>
</head>
<body
data-host="{{ request.get_host }}"
data-scheme="{{ request.scheme }}"
>
<div class="container">
<header>
<nav id="nav" class="nav">{% include 'components/_nav.html' %}</nav>
</header>
<main id="main">{% include page %}</main>
<footer class="footer">My footer</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,54 @@
<ul class="nav__ul" data-controller="navbar">
{# Links always visible #}
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "home" %} active{% endif %}"
data-target="home"
>
Home
</a>
</li>
{% if user.is_authenticated %}
{# Links only if identified #}
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "profile" %} active{% endif %}"
data-target="profile"
>
Profile
</a>
</li>
<li>
<a
href="#"
class="nav__link"
id="logout"
>
Logout
</a>
</li>
{% else %}
{# Links only if not identified #}
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "login" %} active{% endif %}"
data-target="login"
>
Login
</a>
</li>
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "signup" %} active{% endif %}"
data-target="signup"
data-action="click->message#displayUpdateForm"
>
Signup
</a>
</li>
{% endif %}
</ul>

View File

@ -0,0 +1,3 @@
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}

View File

@ -0,0 +1 @@
<h1>404</h1>

View File

@ -0,0 +1,17 @@
<section>
<h1>Welcome to an example of browsing with WebSockets over the Wire</h1>
<p>You will be able to experience a simple structure with a registration, a login and a private page.</p>
</section>
<section>
<h2>Laps</h2>
<p>
<button id="add-lap">Add lap</button>
</p>
<ul id="laps"></ul>
</section>
<section>
<h2>TODO</h2>
<input type="text" id="task">
<button id="add-task">Add task</button>
<ul id="todo"></ul>
</section>

View File

@ -0,0 +1,8 @@
<h1>Login</h1>
<form id="login-form">
{% if user_does_not_exist %}
<h2>The user does not exist or the password is wrong.</h2>
{% endif %}
{{ form.as_p }}
<input type="submit" class="button" value="Login">
</form>

View File

@ -0,0 +1,5 @@
<h1>Profile</h1>
<h2>Username</h2>
<p>{{ user.username }}</p>
<h2>Email</h2>
<p>{{ user.email }}</p>

View File

@ -0,0 +1,11 @@
<h1>Signup</h1>
<form id="signup-form">
{% if user_exist %}
<p>Email already exist</p>
{% endif %}
{% if passwords_do_not_match %}
<p>Passwords do not match.</p>
{% endif %}
{{ form.as_p }}
<input type="submit" class="button" value="Signup">
</form>

41
app/app_template/views.py Normal file
View File

@ -0,0 +1,41 @@
from django.shortcuts import render, redirect
from .forms import LoginForm, SignupForm
from django.contrib.auth.decorators import login_required
def home(request):
return render(
request,
"base.html",
{
"page": "pages/home.html",
"active_nav": "home",
},
)
def login(request):
return render(
request,
"base.html",
{"page": "pages/login.html", "active_nav": "login", "form": LoginForm()},
)
def signup(request):
return render(
request,
"base.html",
{"page": "pages/signup.html", "active_nav": "signup", "form": SignupForm()},
)
@login_required
def profile(request):
return render(
request, "base.html", {"page": "pages/profile.html", "active_nav": "profile"}
)
def page_not_found(request, exception):
return render(request, "base.html", {"page": "pages/404.html"})