login/register/post

This commit is contained in:
j 2023-07-24 22:00:43 +01:00
parent 6f18890739
commit 6fc506df2f
6 changed files with 283 additions and 108 deletions

View file

@ -8,6 +8,7 @@ from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.timesince import timesince
User = get_user_model() User = get_user_model()
@ -133,6 +134,12 @@ class Comment(models.Model):
@property @property
def date(self): def date(self):
now = timezone.now()
difference = now - self.created
if difference <= timedelta(minutes=1):
return "just now"
return '%(time)s ago' % {'time': timesince(self.created).split(', ')[0]}
return self.created.strftime('%B %d, %Y at %H:%M')
return self.created.strftime('%Y-%m-%d %H:%M') return self.created.strftime('%Y-%m-%d %H:%M')
def json(self): def json(self):
@ -143,5 +150,7 @@ class Comment(models.Model):
data['name'] = self.name data['name'] = self.name
data['date'] = self.date data['date'] = self.date
data['text'] = self.text data['text'] = self.text
data['id'] = self.id
data['published'] = self.is_published
return data return data

View file

@ -43,13 +43,7 @@ def item(request, id):
qs = qs.filter(q) qs = qs.filter(q)
comments = [] comments = []
for comment in qs: for comment in qs:
comments.append({ comments.append(comment.json())
"id": comment.id,
"name": comment.name,
"date": comment.date,
"text": comment.text,
"published": comment.is_published,
})
context['comments'] = mark_safe(json.dumps(comments)) context['comments'] = mark_safe(json.dumps(comments))
user = {} user = {}
if request.user.is_staff: if request.user.is_staff:

View file

@ -22,47 +22,69 @@
padding-top: 8px; padding-top: 8px;
padding-left: 4px; padding-left: 4px;
.buttons {
display: none;
&.active {
display: block;
}
}
input, button {
height: 36px;
}
input {
width: calc(50% - 8px);
}
.buttons {
&.login {
input {
width: calc(100% - 8px);
}
}
}
input.invalid,
textarea.invalid {
border-bottom: 1px solid purple;
}
textarea,
input {
padding: 4px;
margin-left: 0;
margin-top: 4px;
margin-bottom: 4px;
margin-right: 4px;
background: none;
color: white;
border: 1px solid green;
}
textarea {
width: calc(100% - 8px);
height: 100px;
display: block;
}
button {
background: black;
color: white;
border: solid 1px green;
padding: 8px;
}
button:hover,
button:active {
border: solid 1px lightgreen;
cursor: pointer;
}
} }
@media(max-width:768px) { @media(max-width:768px) {
.add-comment { .add-comment {
padding-left: 4px; padding-left: 4px;
} input {
} width: calc(100% - 8px);
}
.add-comment textarea, .buttons {
.add-comment input { &.login {
padding: 4px; input {
margin-left: 0; width: calc(100% - 8px);
margin-top: 4px; }
margin-bottom: 4px;
margin-right: 4px;
background: none;
color: white;
border: 1px solid green;
}
.add-comment input {
width: calc(50% - 8px);
}
.add-comment textarea {
width: calc(100% - 8px);
height: 100px;
display: block;
}
.add-comment button {
background: black;
color: white;
border: solid 1px green;
padding: 8px;
}
.add-comment button:hover {
border: solid 1px lightgreen;
cursor: pointer;
}
.add-comment {
.buttons {
&.login {
input {
width: calc(25% - 8px);
} }
} }
} }
@ -99,3 +121,4 @@ button.publish-comment {
height: 16px; height: 16px;
} }
} }

View file

@ -1,14 +1,42 @@
async function publish_comment(id) { function getCookie(name) {
var csrf var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function getCSRF() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value
}
function serializeData(fields) {
var data = {};
document.querySelector('.add-comment').querySelectorAll('input,textarea').forEach(input => { document.querySelector('.add-comment').querySelectorAll('input,textarea').forEach(input => {
if (input.name == 'csrfmiddlewaretoken') { if (fields && !fields.includes(input.name)) {
csrf = input.value.trim() return
}
if (input.name != 'csrfmiddlewaretoken') {
var value = input.value.trim()
if (value) {
data[input.name] = value
}
} }
}) })
var data = { return data
"comment": id }
}
return fetch("/comment/publish/", { async function api(url, data) {
var csrf = getCSRF()
return fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -19,30 +47,26 @@ async function publish_comment(id) {
return response.json() return response.json()
}) })
} }
async function publish_comment(id) {
return api("/comment/publish/", {
"comment": id
})
}
async function login(username, password) { async function logout() {
var csrf return api("/logout/", {}).then(response => {
document.querySelector('.add-comment').querySelectorAll('input,textarea').forEach(input => { delete user.username
if (input.name == 'csrfmiddlewaretoken') {
csrf = input.value.trim()
}
})
var data = {
"username": username,
"password": password,
}
return fetch("/login/", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrf
},
body: JSON.stringify(data)
}).then(response => {
return response.json()
}) })
} }
async function login(data) {
return api("/login/", data)
}
async function register(data) {
return api("/register/", data)
}
function renderComments(cdiv, data) { function renderComments(cdiv, data) {
cdiv.innerHTML = ` cdiv.innerHTML = `
<h3> <h3>
@ -70,7 +94,6 @@ function renderComments(cdiv, data) {
<div class="text">${comment.text}</div> <div class="text">${comment.text}</div>
<div class="meta"> <div class="meta">
by <span class="name">${comment.name}</span> by <span class="name">${comment.name}</span>
on
<span class="date">${comment.date}</span> <span class="date">${comment.date}</span>
${extra} ${extra}
</div> </div>
@ -101,29 +124,31 @@ function renderComments(cdiv, data) {
}) })
} }
function clearInvalid() {
document.querySelectorAll('input.invalid, textarea.invalid').forEach(invalid => {
invalid.classList.remove('invalid')
})
}
document.querySelector('button#add-comment').addEventListener('click', event => { document.querySelector('button#add-comment').addEventListener('click', event => {
var data = {}, csrf; event.preventDefault()
document.querySelector('.add-comment').querySelectorAll('input,textarea').forEach(input => { event.stopPropagation()
if (input.name == 'csrfmiddlewaretoken') { var valid = true, fields = ['text', 'name', 'email']
csrf = input.value.trim() var element = document.querySelector('.add-comment .comment-fields')
} else { element.querySelectorAll('input:invalid, textarea:invalid').forEach(invalid => {
data[input.name] = input.value.trim() if (fields.includes(invalid.name)) {
if (!data[input.name]) { invalid.classList.add('invalid')
delete data[input.name] valid = false
} console.log('invalid', invalid)
} }
}) })
if (!valid) {
return
}
var data = serializeData(fields);
data.item = pandora.comment data.item = pandora.comment
fetch("/comment/", { api("/comment/", data).then(response => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrf
},
body: JSON.stringify(data)
}).then(response => {
return response.json()
}).then(response => {
var comment = document.createElement('div') var comment = document.createElement('div')
comment.classList.add('comment') comment.classList.add('comment')
comment.innerHTML = ` comment.innerHTML = `
@ -139,5 +164,119 @@ document.querySelector('button#add-comment').addEventListener('click', event =>
comment.querySelector('.text').innerText = response.text comment.querySelector('.text').innerText = response.text
document.querySelector('.comments .comments-content').append(comment) document.querySelector('.comments .comments-content').append(comment)
document.querySelector('.add-comment textarea').value = '' document.querySelector('.add-comment textarea').value = ''
if (!user.username) {
localStorage.name = data.name
localStorage.email = data.email
}
}) })
}) })
function afterLogin() {
document.querySelector('input[name="csrfmiddlewaretoken"]').value = getCookie('csrftoken')
user.username = data.username
document.querySelectorAll('.add-comment input[name="email"]').forEach(input => {
input.required = false
})
document.querySelectorAll('.add-comment input[type="password"]').forEach(input => {
input.value = ""
input.required = false
})
document.querySelector('.add-comment .buttons.login').classList.remove('active')
var buttons = document.querySelector('.add-comment .buttons.guest')
buttons.querySelector('#add-comment').innerText = "Add comment"
buttons.querySelector('#login').remove()
buttons.classList.remove('guest')
buttons.classList.add('active')
var textarea = document.querySelector('.add-comment textarea')
textarea.style.display = ""
textarea.required = true
document.querySelector('.add-comment .login .error').innerText = ''
delete localStorage.name
delete localStorage.email
}
var btnLogin = document.querySelector('.add-comment .buttons.guest button#login')
if (btnLogin) {
btnLogin.addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
clearInvalid()
document.querySelector('.add-comment .buttons.guest').classList.remove('active')
document.querySelector('.add-comment .user-info').style.display = "none"
document.querySelector('.add-comment .buttons.login').classList.add('active')
var textarea = document.querySelector('.add-comment textarea')
textarea.style.display = "none"
textarea.required = false
})
document.querySelector('.add-comment .buttons.login button#login').addEventListener("click", event => {
event.preventDefault()
event.stopPropagation()
clearInvalid()
if (!document.querySelectorAll('.add-comment .login input:invalid').length) {
event.target.disabled = true
var data = serializeData()
login({
"username": data.username,
"password": data.password,
}).then(response => {
if (response.error) {
document.querySelector('.add-comment .login .error').innerText = response.error
event.target.disabled = false
} else {
afterLogin()
}
})
} else {
document.querySelectorAll('.add-comment .login input:invalid').forEach(invalid => {
invalid.classList.add('invalid')
})
document.querySelector('.add-comment .login .error').innerText = ''
}
})
document.querySelector('.add-comment .buttons.login button#register').addEventListener("click", event => {
event.preventDefault()
event.stopPropagation()
clearInvalid()
var login = document.querySelector('.add-comment .buttons.login button#login')
var email = document.querySelector('.add-comment .buttons.login input[name="email"]')
if (login.style.display == "none") {
if (!document.querySelectorAll('.add-comment .login input:invalid').length) {
event.target.disabled = true
var data = serializeData()
register({
"username": data.username,
"password": data.password,
"email": data.email,
}).then(response => {
if (response.error) {
document.querySelector('.add-comment .login .error').innerText = response.error
event.target.disabled = false
} else {
afterLogin()
}
})
} else {
document.querySelectorAll('.add-comment .login input:invalid').forEach(invalid => {
invalid.classList.add('invalid')
})
document.querySelector('.add-comment .login .error').innerText = ''
}
} else {
document.querySelector('.add-comment .login .error').innerText = ''
login.style.display = "none"
email.required = true
email.style.display = "block"
}
})
}
if (!user.username) {
if (localStorage.name) {
document.querySelector('.add-comment .comment-fields input[name="name"]').value = localStorage.name
}
if (localStorage.email) {
document.querySelector('.add-comment .comment-fields input[name="email"]').value = localStorage.email
}
}

View file

@ -5,31 +5,36 @@
{% block main %} {% block main %}
<div class="content"> <div class="content">
</div> </div>
<div class="add-comment" style="display: none"> <form class="add-comment" style="display: none">
<div class="comment-fields">
{% csrf_token %} {% csrf_token %}
<textarea name="text" placeholder="your comment"></textarea> <textarea name="text" placeholder="your comment" required></textarea>
<div class="user"> <div class="user">
{% if request.user.is_anonymous %} {% if request.user.is_anonymous %}
<input name="name" type="text" placeholder="your name"></input> <div class="user-info">
<input name="email" type="email" placeholder="your email"></input> <input name="name" type="text" placeholder="your name" required></input>
<br> <input name="email" type="email" placeholder="your email" required></input>
<div class="buttons"> </div>
</div>
<div class="buttons guest active">
<button id="add-comment">Add comment as guest</button> <button id="add-comment">Add comment as guest</button>
<button>Login</button> <button id="login">Login</button>
</div> </div>
<div class="buttons login"> <div class="buttons login">
<input name="username" type="text" placeholder="your username"></input> <input name="username" type="text" placeholder="your username" required></input>
<input name="password" type="password" placeholder="your password"></input> <input name="password" type="password" placeholder="your password" required></input>
<button>Login</button> <input name="email" type="email" placeholder="your email" style="display: none"></input>
<button>Register</button> <button id="login">Login</button>
<button id="register">Register</button>
<div class="error"></div>
</div> </div>
{% else %} {% else %}
<div class="buttons"> <div class="buttons active">
<button id="add-comment">Add comment</button> <button id="add-comment">Add comment</button>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </form>
{% endblock %} {% endblock %}
{% block end %} {% block end %}
<script> <script>

View file

@ -12,7 +12,7 @@ from brake.decorators import ratelimit
User = get_user_model() User = get_user_model()
@ratelimit(method="POST", block=True, rate="1/m") @ratelimit(method="POST", block=True, rate="5/m")
def register(request): def register(request):
response = {} response = {}
data = json.loads(request.body) data = json.loads(request.body)
@ -33,7 +33,7 @@ def register(request):
return render_to_json(response) return render_to_json(response)
@ratelimit(method="POST", block=True, rate="1/m") @ratelimit(method="POST", block=True, rate="5/m")
def login(request): def login(request):
response = {} response = {}
data = json.loads(request.body) data = json.loads(request.body)
@ -41,10 +41,15 @@ def login(request):
if user is not None and user.is_active: if user is not None and user.is_active:
django.contrib.auth.login(request, user) django.contrib.auth.login(request, user)
response['user'] = user.username response['user'] = user.username
else:
response['error'] = 'login failed'
return render_to_json(response) return render_to_json(response)
def logout(request): def logout(request):
if request.user.is_authenticated: if request.user.is_authenticated:
django.contrib.auth.logout(request) django.contrib.auth.logout(request)
redirect('/') if request.method == "POST":
data = json.loads(request.body)
return render_to_json({})
return redirect('/')