Permalink
Please sign in to comment.
Browse files
Revise comments to support user-supplied names and URLs
Standard blog comments allow you to not just give your actual comment but also tell people your name and often a website URL. DWiki has allowed this implicitly (you could manually add some sort of a signature if you wanted to and some people do), but there is a lot of merit in supporting it explicitly in the way that people expect and recognize. Also, explicit identification allows DWiki to show it instead of the submission IP in various 'who wrote this' bits. - new comment format in model.py / model_comment.py - new quoting function in httputil.py - actual support for all of this in comments.py, with related changes and cleanups in atomgen.py and macros.py - the comment-related global variables have changed, so document that - templates changed to actually show this information where appropriate DWiki tries hard to reject usernames and urls with dangerous things (especially newlines, which would break the storage format) and correctly quote what remains but I may find that it is not sufficient. (It does not attempt to sanitize things exactly; if there is bad stuff it simply nulls the field. Better safe than sorry.)
- Loading branch information...
Showing
with
285 additions
and 70 deletions.
- +1 −3 atomgen.py
- +91 −8 comments.py
- +7 −0 httputil.py
- +5 −1 macros.py
- +8 −45 model.py
- +150 −0 model_comment.py
- +8 −5 test.timestamps
- +6 −1 test/pages/dwiki/GlobalVariables
- +1 −0 test/templates/comment/author
- +1 −0 test/templates/comment/authorship
- +1 −1 test/templates/comment/header.tmpl
- +1 −1 test/templates/comment/user
- +2 −3 test/templates/comment/writebody-anon.tmpl
- +1 −0 test/templates/syndication/atomcomment-url
- +2 −2 test/templates/syndication/atomcomment.tmpl
4
atomgen.py
99
comments.py
7
httputil.py
6
macros.py
53
model.py
150
model_comment.py
| @@ -0,0 +1,150 @@ | ||
| +# | ||
| +# The specific model classes and functions for comments. | ||
| +# This is separate from model.py because it is getting big. | ||
| + | ||
| +import re | ||
| + | ||
| +import utils | ||
| +import pages | ||
| + | ||
| +import derrors, storage | ||
| + | ||
| +# --- | ||
| +# Original (legacy) comment format, which has only DWiki login and | ||
| +# comment IP address. | ||
| + | ||
| +# Comments are stored in a packed form. | ||
| +commentbody_re = re.compile("USER ([^\n]+)\nIP ([^\n]+)\n(.*)$", | ||
| + re.DOTALL) | ||
| +class Comment: | ||
| + def __init__(self, context = None, data = None): | ||
| + if context: | ||
| + self.user = context.login | ||
| + self.ip = context["remote-ip"] | ||
| + else: | ||
| + self.user = None | ||
| + self.ip = None | ||
| + if not data: | ||
| + self.data = '' | ||
| + else: | ||
| + self.data = data | ||
| + self.time = None | ||
| + self.name = None | ||
| + self.username = '' | ||
| + self.userurl = '' | ||
| + self.anon = None | ||
| + def __str__(self): | ||
| + if not self.data: | ||
| + return '' | ||
| + return 'USER %s\nIP %s\n%s' % (self.user, self.ip, self.data) | ||
| + def fromstore(self, fileobj, name): | ||
| + blob = fileobj.contents() | ||
| + if not blob: | ||
| + return False | ||
| + mo = commentbody_re.match(blob) | ||
| + if not mo: | ||
| + return False | ||
| + self.user = mo.group(1) | ||
| + self.ip = mo.group(2) | ||
| + self.data = mo.group(3) | ||
| + self.time = fileobj.timestamp() | ||
| + self.name = name | ||
| + return True | ||
| + | ||
| + def is_anon(self, context): | ||
| + return self.user == context.default_user() | ||
| + | ||
| +# --- | ||
| +# New format comments now include an explicit version field as the first | ||
| +# field. | ||
| +commentver_re = re.compile("^VER (\d+)\n") | ||
| + | ||
| +# V1 new comment format. This adds a user-supplied name and URL so that | ||
| +# DWiki acts more like traditional blog comments and while I'm at it, | ||
| +# a marker of whether the DWiki login was the default/anonymous/guest | ||
| +# user at the time of comment submission (... in case you remove or | ||
| +# change it later or something). | ||
| +# | ||
| +commentv1_re = re.compile("^VER 1\nUSER ([^\n]+)\nANON (Yes|No)\nNAME ([^\n]*)\nURL ([^\n]*)\nIP ([^\n]+)\n(.*)$", | ||
| + re.DOTALL) | ||
| +class CommentV1: | ||
| + def __init__(self): | ||
| + self.user = None | ||
| + self.anon = None | ||
| + self.ip = None | ||
| + self.data = '' | ||
| + self.username = '' | ||
| + self.userurl = '' | ||
| + self.time = None | ||
| + self.name = None | ||
| + | ||
| + def __str__(self): | ||
| + if not self.data: | ||
| + return '' | ||
| + return "VER 1\nUSER %s\nANON %s\nNAME %s\nURL %s\nIP %s\n%s" % \ | ||
| + (self.user, self.anon, | ||
| + self.username, self.userurl, self.ip, | ||
| + self.data) | ||
| + # Called only if the blob is non-null and asserts to be a V1 format. | ||
| + def fromstore(self, fileobj, name): | ||
| + blob = fileobj.contents() | ||
| + if not blob: | ||
| + raise derrors.IntErr("CommentV1 fromstore blob is empty") | ||
| + mo = commentv1_re.match(blob) | ||
| + if not mo: | ||
| + return False | ||
| + self.user = mo.group(1) | ||
| + self.anon = mo.group(2) | ||
| + self.username = mo.group(3).strip() | ||
| + self.userurl = mo.group(4).strip() | ||
| + self.ip = mo.group(5) | ||
| + self.data = mo.group(6) | ||
| + self.time = fileobj.timestamp() | ||
| + self.name = name | ||
| + return True | ||
| + | ||
| + def fromform(self, context, data, username, userurl): | ||
| + self.user = context.login | ||
| + self.ip = context["remote-ip"] | ||
| + self.data = data | ||
| + self.username = username | ||
| + self.userurl = userurl | ||
| + if context.is_login_default(): | ||
| + self.anon = "Yes" | ||
| + else: | ||
| + self.anon = "No" | ||
| + | ||
| + def is_anon(self, _): | ||
| + return self.anon == "Yes" | ||
| + | ||
| +# ---- | ||
| +# This loads comments in any known format from the disk, working out | ||
| +# which format the comment is in itself. Returns the comment or None | ||
| +# and may raise derrors.IntErr in some situations. | ||
| + | ||
| +def loadcomment(fileobj, name): | ||
| + blob = fileobj.contents() | ||
| + if not blob: | ||
| + return None | ||
| + mo = commentver_re.match(blob) | ||
| + # might be a version zero comment. | ||
| + if not mo: | ||
| + mo = commentbody_re.match(blob) | ||
| + if mo: | ||
| + c = Comment() | ||
| + else: | ||
| + return None | ||
| + elif mo.group(1) == "1": | ||
| + c = CommentV1() | ||
| + else: | ||
| + raise derrors.IntErr("Uknown comment format version: '%s' in %s" % | ||
| + (mo.group(1), name)) | ||
| + | ||
| + # Load: | ||
| + if c.fromstore(fileobj, name): | ||
| + return c | ||
| + else: | ||
| + return None | ||
| + | ||
| +# TODO: should we have a createcomment() function? Probably not, since the | ||
| +# arguments are likely to keep evolving. |
13
test.timestamps
7
test/pages/dwiki/GlobalVariables
1
test/templates/comment/author
| @@ -0,0 +1 @@ | ||
| +By ${!comment-name} |
1
test/templates/comment/authorship
| @@ -0,0 +1 @@ | ||
| +%{comment::author} |
2
test/templates/comment/header.tmpl
| @@ -1 +1 @@ | ||
| -#{|comment/user|comment/ip} at @{comment::date} | ||
| +#{|comment/authorship|comment/user|comment/ip} at @{comment::date} |
2
test/templates/comment/user
| @@ -1 +1 @@ | ||
| -By %{comment::user} | ||
| +By ${!comment-login} |
5
test/templates/comment/writebody-anon.tmpl
| @@ -1,3 +1,2 @@ | ||
| -%{cond::anonymous}<p> Please remember to sign your comment; otherwise, | ||
| -the only author identification will be your IP address. Your IP address | ||
| -will be shown with your posted comment. </p> | ||
| +%{cond::anonymous}<p> Please note that your IP address will be shown with | ||
| +your comment if you don't otherwise identify yourself. </p> |
1
test/templates/syndication/atomcomment-url
| @@ -0,0 +1 @@ | ||
| +<uri>${!comment-url}</uri> |
4
test/templates/syndication/atomcomment.tmpl
| @@ -1,8 +1,8 @@ | ||
| <entry> | ||
| -<title type="html">#{|comment/user|comment/ip} on /${page}</title> | ||
| +<title type="html">#{|comment/author|comment/user|comment/ip} on /${page}</title> | ||
| <id>tag:${wikiname}:${page}:${:comment:name}</id> | ||
| <link rel="alternate" type="text/html" href="@{atom::commenturl}" /> | ||
| -<author><name>#{|comment/user|comment/ip}</name></author> | ||
| +<author><name>#{|comment/author|comment/user|comment/ip}</name>#{syndication/atomcomment-url}</author> | ||
| <content type="html">%{atom::comment}</content> | ||
| <updated>@{atom::commentstamp}</updated> | ||
| </entry> |
0 comments on commit
e750c03