First commit
This commit is contained in:
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
0
app/app_template/__init__.py
Normal file
0
app/app_template/__init__.py
Normal file
116
app/app_template/actions.py
Normal file
116
app/app_template/actions.py
Normal 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
6
app/app_template/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SimpleAppConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "app.app_template"
|
35
app/app_template/backends.py
Normal file
35
app/app_template/backends.py
Normal 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
|
55
app/app_template/consumers.py
Normal file
55
app/app_template/consumers.py
Normal 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
39
app/app_template/forms.py
Normal 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"}
|
||||
),
|
||||
)
|
0
app/app_template/migrations/__init__.py
Normal file
0
app/app_template/migrations/__init__.py
Normal file
3
app/app_template/models.py
Normal file
3
app/app_template/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
23
app/app_template/templates/base.html
Normal file
23
app/app_template/templates/base.html
Normal 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>
|
54
app/app_template/templates/components/_nav.html
Normal file
54
app/app_template/templates/components/_nav.html
Normal 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>
|
3
app/app_template/templates/components/_tasks.html
Normal file
3
app/app_template/templates/components/_tasks.html
Normal file
@ -0,0 +1,3 @@
|
||||
{% for task in tasks %}
|
||||
<li>{{ task }}</li>
|
||||
{% endfor %}
|
1
app/app_template/templates/pages/404.html
Normal file
1
app/app_template/templates/pages/404.html
Normal file
@ -0,0 +1 @@
|
||||
<h1>404</h1>
|
17
app/app_template/templates/pages/home.html
Normal file
17
app/app_template/templates/pages/home.html
Normal 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>
|
8
app/app_template/templates/pages/login.html
Normal file
8
app/app_template/templates/pages/login.html
Normal 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>
|
5
app/app_template/templates/pages/profile.html
Normal file
5
app/app_template/templates/pages/profile.html
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Profile</h1>
|
||||
<h2>Username</h2>
|
||||
<p>{{ user.username }}</p>
|
||||
<h2>Email</h2>
|
||||
<p>{{ user.email }}</p>
|
11
app/app_template/templates/pages/signup.html
Normal file
11
app/app_template/templates/pages/signup.html
Normal 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
41
app/app_template/views.py
Normal 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"})
|
Reference in New Issue
Block a user