Give Users a display_name.

This commit is contained in:
voussoir 2018-04-15 14:23:24 -07:00
parent 8562b355ce
commit 21bd211889
9 changed files with 84 additions and 11 deletions

View file

@ -60,7 +60,6 @@ Here is a brief overview of the project to help you learn your way around:
- Add a `Photo.merge` to combine duplicate entries.
- Generate thumbnails for vector files without falling victim to bombs.
- Allow photos to have nonstandard, orderby-able properties like "release year". How?
- When users have '%' or '#', etc. in their username, it is difficult to access their /user/ URL. I would prefer to fix it without simply blacklisting those characters.
- Currently, the Jinja templates are having a tangling influence on the backend objects, because Jinja cannot import my other modules like bytestring, but it can access the methods of the objects I pass into the template. As a result, the objects have excess helper methods. Consider making them into Jinja filters instead. Which is also kind of ugly but will move that pollution out of the backend at least.
- Perhaps instead of actually deleting objects, they should just have a `deleted` flag, to make easy restoration possible. Also consider regrouping the children of restored Groupables if those children haven't already been reassigned somewhere else.
- Add a new table to store permanent history of add/remove of tags on photos, so that accidents or trolling can be reversed.

View file

@ -42,7 +42,7 @@ FILENAME_BADCHARS = '\\/:*?<>|"'
# Note: Setting user_version pragma in init sequence is safe because it only
# happens after the out-of-date check occurs, so no chance of accidentally
# overwriting it.
DATABASE_VERSION = 12
DATABASE_VERSION = 13
DB_INIT = f'''
PRAGMA cache_size = 10000;
PRAGMA count_changes = OFF;
@ -54,6 +54,7 @@ CREATE TABLE IF NOT EXISTS users(
id TEXT PRIMARY KEY NOT NULL,
username TEXT NOT NULL COLLATE NOCASE,
password BLOB NOT NULL,
display_name TEXT,
created INT
);
CREATE INDEX IF NOT EXISTS index_users_id on users(id);
@ -291,6 +292,7 @@ DEFAULT_CONFIGURATION = {
'new': True,
},
'user': {
'edit': True,
'login': True,
'new': True,
},
@ -305,8 +307,9 @@ DEFAULT_CONFIGURATION = {
'user': {
'min_length': 2,
'min_password_length': 6,
'max_display_name_length': 24,
'max_length': 24,
'valid_chars': string.ascii_letters + string.digits + '~!@#$%^*()[]{}:;,.<>/\\-_+=',
'valid_chars': string.ascii_letters + string.digits + '_-',
},
'digest_exclude_files': [

View file

@ -147,6 +147,9 @@ class UsernameTooLong(InvalidUsername):
class UsernameTooShort(InvalidUsername):
error_message = 'Username "{username}" is shorter than minimum of {min_length}.'
class DisplayNameTooLong(EtiquetteException):
error_message = 'Display name "{display_name}" is longer than maximum of {max_length}.'
class WrongLogin(EtiquetteException):
error_message = 'Wrong username-password combination.'

View file

@ -49,12 +49,16 @@ class ObjectBase:
@staticmethod
def normalize_author_id(author_id):
if author_id is None or author_id == '':
if author_id is None:
return None
if not isinstance(author_id, str):
raise TypeError('author_id must be string, not %s' % type(author_id))
author_id = author_id.strip()
if author_id == '':
return None
return author_id
def get_author(self):
@ -1476,6 +1480,7 @@ class User(ObjectBase):
self.username = db_row['username']
self.created = db_row['created']
self.password_hash = db_row['password']
self._display_name = self.normalize_display_name(db_row['display_name'])
def __repr__(self):
rep = f'User:{self.id}:{self.username}'
@ -1485,6 +1490,50 @@ class User(ObjectBase):
rep = f'User:{self.username}'
return rep
@staticmethod
def normalize_display_name(display_name, max_length=None):
if display_name is None:
return None
if not isinstance(display_name, str):
raise TypeError('Display Name must be string, not %s' % type(display_name))
display_name = display_name.strip()
if display_name == '':
return None
if max_length is not None and len(display_name) > max_length:
raise exceptions.DisplayNameTooLong(display_name=display_name, max_length=max_length)
return display_name
@property
def display_name(self):
if self._display_name is None:
return self.username
else:
return self._display_name
@decorators.required_feature('user.edit')
@decorators.transaction
def set_display_name(self, display_name, *, commit=True):
display_name = self.normalize_display_name(
display_name,
max_length=self.photodb.config['user']['max_display_name_length'],
)
data = {
'id': self.id,
'display_name': display_name,
}
self.photodb.sql_update(table='users', pairs=data, where_key='id')
self._display_name = display_name
if commit:
self.photodb.log.debug('Committing - set display name')
self.photodb.commit()
class WarningBag:
def __init__(self):

View file

@ -4,10 +4,10 @@
<a class="header_element" href="/search">Search</a>
<a class="header_element" href="/tags">Tags</a>
{% if session.user %}
<a class="header_element" href="/user/{{session.user.username}}">{{session.user.username}}</a>
<a class="header_element" href="/user/{{session.user.username}}">{{session.user.display_name}}</a>
<a class="header_element" href="/logout" style="flex:0">Logout</a>
{% else %}
<a class="header_element" href="/login">Log in</a>
{% endif %}
</div>
{% endmacro %}
{% endmacro %}

View file

@ -186,7 +186,7 @@
<li>Filename: {{photo.basename}}</li>
{% set author = photo.get_author() %}
{% if author is not none %}
<li>Author: <a href="/user/{{author.username}}">{{author.username}}</a></li>
<li>Author: <a href="/user/{{author.username}}">{{author.display_name}}</a></li>
{% endif %}
{% if photo.width %}
<li>Dimensions: {{photo.width}}x{{photo.height}} px</li>

View file

@ -44,7 +44,7 @@ body, .nice_link
<a class="nice_link" href="/albums">Browse albums</a>
<a class="nice_link" href="/bookmarks">Bookmarks</a>
{% if session.user %}
<a class="nice_link" href="/user/{{session.user.username}}">{{session.user.username}}</a>
<a class="nice_link" href="/user/{{session.user.username}}">{{session.user.display_name}}</a>
{% else %}
<a class="nice_link" href="/login">Log in</a>
{% endif %}

View file

@ -2,7 +2,7 @@
<html>
<head>
{% import "header.html" as header %}
<title>User {{user.username}}</title>
<title>User {{user.display_name}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/static/css/common.css">
@ -20,9 +20,9 @@
<body>
{{header.make_header(session=session)}}
<div id="content_body">
<h2>{{user.username}}</h2>
<h2>{{user.display_name}}</h2>
<p>ID: {{user.id}}</p>
<p><a href="/search?author={{user.username}}">Photos by {{user.username}}</a></p>
<p><a href="/search?author={{user.username}}">Photos by {{user.display_name}}</a></p>
</div>
</body>

View file

@ -238,6 +238,25 @@ def upgrade_11_to_12(photodb):
'''
photodb.sql.cursor().execute(query)
def upgrade_12_to_13(photodb):
'''
Added display_name column to the User table.
'''
cur = photodb.sql.cursor()
cur.execute('PRAGMA foreign_keys = OFF')
cur.execute('ALTER TABLE users RENAME TO users_old')
cur.execute('''
CREATE TABLE users(
id TEXT PRIMARY KEY NOT NULL,
username TEXT NOT NULL COLLATE NOCASE,
password BLOB NOT NULL,
display_name TEXT,
created INT
)''')
cur.execute('INSERT INTO users SELECT id, username, password, NULL, created FROM users_old')
cur.execute('DROP TABLE users_old')
cur.execute('PRAGMA foreign_keys = ON')
def upgrade_all(data_directory):
'''
Given the directory containing a phototagger database, apply all of the