first commit

This commit is contained in:
Iris Lightshard 2020-04-28 19:01:19 -04:00
commit 8aee826f7d
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
12 changed files with 361 additions and 0 deletions

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2020, Derek Stevens
drkste@zoho.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# [[ comments ]]
### - a simple embeddable comment system for Django -
## about
`comments` is an unceremoniously named comment system created with the Django framework with the intention of adding comment capabilities to an otherwise statically generated site. Together with my `ayanami` CMS this provides a featureful and lightweight web platform for content generation, sharing, and discussion.
## usage
`comments` is a Django app. Thus, to use it, drop the `comments` directory into your django project directory, include the `urls.py` into the global one, and probably copy or move the `ext.py` to the django global directory, editing it to match your djang project's name.
Once your django setup is done, you can call `ext.py create *` from your static site generator to create comment threads, and embed iframes pointing to `/your/django/dir/comments/thread` in your page. To manage comments, you can use the vanilla django admin console. You can start at a thread, and from the `root_comment` through each `next` comment you can use the `change` directive to follow the comment thread to the comment of interest; or you can manage by comment directly. Comments can be hidden to avoid manually the relinking the threads after actually deleting them.
## data
Comments are stored in a linked list.
Each thread is just:
* `thread_id`: a unique identifier (primary key) for the thread
* `root_comment`: the first comment in the thread; `None` if empty
And each comment is structured as:
* `comment_author`
* `comment_author_email`: this is only used internally for accountability reasons
* `comment_date`: this is automatically generated when the comment is created
* `hidden`: a boolean flag whether to show the comment or not
* `comment_data`: the textual content of the comment
* `next`: the next comment in the thread; `None` if the last
## licensing
`comments` is released under a 2-clause BSD License (`LICENSE` file). Use it however you want as long as you reproduce the `LICENSE` in the distribution and allow access to the source.

0
__init__.py Normal file
View file

6
admin.py Normal file
View file

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import Thread, Comment
admin.site.register(Thread)
admin.site.register(Comment)

5
apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CommentsConfig(AppConfig):
name = 'comments'

51
ext.py Normal file
View file

@ -0,0 +1,51 @@
# comments/ext.py
# (c) 2020 Derek Stevens <drkste@zoho.com>
# this is a helper script to initialize comment threads externally
# move this to the project directory and change the settings imports accordingly
import sys
from django.conf import settings
import nilfm.settings as nilfm_settings
settings.configure(INSTALLED_APPS=nilfm_settings.INSTALLED_APPS, DATABASES=nilfm_settings.DATABASES)
import django
django.setup()
from comments.models import Comment, Thread
def echo(*args):
threads = Thread.objects.all();
for t in threads:
print(t)
c = t.root_comment
print(c)
if c:
c = c.next
print(c)
def create(id):
x = Thread(thread_id=id, )
x.save()
def postTo(**kwargs):
id = kwargs["id"]
name = kwargs["name"]
mail = kwargs["mail"]
data = kwargs["data"]
t = Thread.objects.get(pk=id)
current = t.root_comment
if current:
while current:
current = current.next
current = Comment(comment_author=name, comment_author_email=mail, comment_data=data)
current.save()
options = {
"echo": echo,
"create": create,
"postTo": postTo
}
options[sys.argv[1]](sys.argv[2:])

22
models.py Normal file
View file

@ -0,0 +1,22 @@
# comments/models.py
# (c) 2020 Derek Stevens <drkste@zoho.com>
from django.db import models
from datetime import datetime
class Comment(models.Model):
comment_author = models.CharField(max_length=128, blank=False)
comment_author_email = models.CharField(max_length=128, blank=False)
comment_date = models.DateTimeField(default=datetime.now, blank=True)
comment_data = models.CharField(max_length=4096, blank=False)
hidden = models.BooleanField(default=False)
next = models.ForeignKey('self', on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.comment_author + " <" + self.comment_author_email + "> @" + self.comment_date.strftime('%Y-%m-%d %H:%M') + ": " + self.comment_data
class Thread(models.Model):
thread_id = models.CharField(primary_key=True, max_length=64)
root_comment = models.ForeignKey(Comment, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.thread_id

68
static/thread.css Normal file
View file

@ -0,0 +1,68 @@
body
{
font-family: Monospace;
font-size: 10px;
color: #797979;
background-color: #000000;
}
#main
{
}
#commentwrapper
{
position: relative;
display: grid;
grid-template-rows: 18px 18px 1fr;
grid-template-columns: 1fr;
}
.author
{
color: #c4c4c4;
grid-row: 1;
padding-top: 8px;
}
.datetime
{
color: #3f3f3f;
grid-row: 2;
padding-left: 4px;
}
.commentdata
{
grid-row: 3;
padding-left: 4px;
}
#errormsg
{
color: #c43f3f
}
.myInputs
{
border: 1px solid #3f3f3f;
}
.myButton
{
font-weight: bold;
color: #3b9088;
background-color: #000000;
border: none;
}
.myButton:hover
{
color: #6aa6a0;
}
textarea
{
width: 100%;
height: 48px;
}

View file

@ -0,0 +1,43 @@
{% load static %}
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="lair of nilix" />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>comments for {{ thread.thread_id }}</title>
<link rel="shortcut icon" href="/favicon.ico">
<link rel="stylesheet" type="text/css" href="{% static "thread.css" %} ">
</head>
<body>
<div id="main">
{% for comment in comments %}
<div class="commentwrapper">
<div class="author">{{ comment.comment_author }}</div>
<div class="datetime">{{ comment.comment_date }}</div>
<div class="commentdata">{{ comment.comment_data }}</div>
</div>
{% empty %}
<p>No comments yet!</p>
{% endfor %}
<p> Post a comment</p>
{% if error_message %}
<b id="errormsg"> {{ error_message }} </b>
{% endif %}
<div class="formwrapper">
<form action = "{% url 'comments:post' thread.thread_id %}" method="POST">
{% csrf_token %}
name:<br/>
<input type="text" name="comment_author" class="myInputs" maxlength=128/><br/>
email:<br/>
<input type="text" name="comment_author_email" class="myInputs" maxlength=128/><br/>
comment:<br/>
<textarea name="comment_data" class="myInputs" rows="10" cols="70" wrap="hard"></textarea><br/>
<input type="submit" value="Post" class="myButton"/>
</form><br/>
</div>
</div>
</body>
</html>

3
tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
urls.py Normal file
View file

@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = "comments"
urlpatterns = [
path('<str:thread_id>/', views.thread, name='thread'),
path('<str:thread_id>/post/', views.post, name='post'),
]

102
views.py Normal file
View file

@ -0,0 +1,102 @@
# Comments/views.py
# (c) 2020 Derek Stevens <drkste@zoho.com>
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from .models import Comment, Thread
from django.template import loader
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.core.validators import ValidationError
from django.views.decorators.clickjacking import xframe_options_sameorigin
def buildCommentList(cThread):
cList = None
if cThread and cThread.root_comment:
current = cThread.root_comment
if not current.hidden:
cList = [ current ]
while current.next:
current = current.next
if not current.hidden:
if cList:
cList.append(current)
else:
cList = [ current ]
return cList
@xframe_options_sameorigin
def thread(request, thread_id):
cThread = get_object_or_404(Thread, pk=thread_id)
commentList = buildCommentList(cThread)
template = loader.get_template('comments/thread.html')
context = { 'thread': cThread, 'comments': commentList }
return HttpResponse(template.render(context, request))
def checkMailAddr(addr):
if "@" in addr:
if addr[0] == "@":
raise ValidationError("Invalid email address!")
domain = addr.split("@")[1]
if "." in domain and len(domain) >= 5:
for i in domain.split("."):
if len(i) < 2:
raise ValidationError("Invalid email address!")
return 1
else:
raise ValidationError("Invalid email address!")
else:
raise ValidationError("Invalid email address!")
def checkLength(name, x):
if len(name) > x:
return 1
else:
raise ValidationError("Not enough characters in field!")
@xframe_options_sameorigin
def post(request, thread_id):
cThread = get_object_or_404(Thread, pk=thread_id)
template = loader.get_template('comments/thread.html')
commentList = buildCommentList(cThread)
context = {'thread': cThread, 'comments': commentList}
if request.POST:
name = request.POST['comment_author']
mail = request.POST['comment_author_email']
data = request.POST['comment_data']
try:
validationCounter = 0
validationCounter += checkLength(name, 1)
validationCounter += checkMailAddr(mail)
validationCounter += checkLength(data, 8)
except ValidationError:
if validationCounter == 0:
context['error_message'] = "What was your name again?"
if validationCounter == 1:
context['error_message'] = "Enter a valid e-mail address, please. It is only recorded for accountability; it is not publicized."
if validationCounter == 2:
context['error_message'] = "Say something meaningful! At least 8 characters are required for the comment field."
return HttpResponse(template.render(context, request))
newComment = Comment(comment_author=name, comment_author_email=mail, comment_data=data)
newComment.save()
if cThread.root_comment:
c = cThread.root_comment
while c:
last = c
c = c.next
last.next = newComment
last.save()
else:
cThread.root_comment = newComment
cThread.save()
return HttpResponseRedirect(reverse('comments:thread', args=(thread_id,)))