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. - Add a `Photo.merge` to combine duplicate entries.
- Generate thumbnails for vector files without falling victim to bombs. - Generate thumbnails for vector files without falling victim to bombs.
- Allow photos to have nonstandard, orderby-able properties like "release year". How? - 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. - 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. - 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. - 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 # 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 # happens after the out-of-date check occurs, so no chance of accidentally
# overwriting it. # overwriting it.
DATABASE_VERSION = 12 DATABASE_VERSION = 13
DB_INIT = f''' DB_INIT = f'''
PRAGMA cache_size = 10000; PRAGMA cache_size = 10000;
PRAGMA count_changes = OFF; PRAGMA count_changes = OFF;
@ -54,6 +54,7 @@ CREATE TABLE IF NOT EXISTS users(
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
username TEXT NOT NULL COLLATE NOCASE, username TEXT NOT NULL COLLATE NOCASE,
password BLOB NOT NULL, password BLOB NOT NULL,
display_name TEXT,
created INT created INT
); );
CREATE INDEX IF NOT EXISTS index_users_id on users(id); CREATE INDEX IF NOT EXISTS index_users_id on users(id);
@ -291,6 +292,7 @@ DEFAULT_CONFIGURATION = {
'new': True, 'new': True,
}, },
'user': { 'user': {
'edit': True,
'login': True, 'login': True,
'new': True, 'new': True,
}, },
@ -305,8 +307,9 @@ DEFAULT_CONFIGURATION = {
'user': { 'user': {
'min_length': 2, 'min_length': 2,
'min_password_length': 6, 'min_password_length': 6,
'max_display_name_length': 24,
'max_length': 24, 'max_length': 24,
'valid_chars': string.ascii_letters + string.digits + '~!@#$%^*()[]{}:;,.<>/\\-_+=', 'valid_chars': string.ascii_letters + string.digits + '_-',
}, },
'digest_exclude_files': [ 'digest_exclude_files': [

View file

@ -147,6 +147,9 @@ class UsernameTooLong(InvalidUsername):
class UsernameTooShort(InvalidUsername): class UsernameTooShort(InvalidUsername):
error_message = 'Username "{username}" is shorter than minimum of {min_length}.' 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): class WrongLogin(EtiquetteException):
error_message = 'Wrong username-password combination.' error_message = 'Wrong username-password combination.'

View file

@ -49,12 +49,16 @@ class ObjectBase:
@staticmethod @staticmethod
def normalize_author_id(author_id): def normalize_author_id(author_id):
if author_id is None or author_id == '': if author_id is None:
return None return None
if not isinstance(author_id, str): if not isinstance(author_id, str):
raise TypeError('author_id must be string, not %s' % type(author_id)) 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 return author_id
def get_author(self): def get_author(self):
@ -1476,6 +1480,7 @@ class User(ObjectBase):
self.username = db_row['username'] self.username = db_row['username']
self.created = db_row['created'] self.created = db_row['created']
self.password_hash = db_row['password'] self.password_hash = db_row['password']
self._display_name = self.normalize_display_name(db_row['display_name'])
def __repr__(self): def __repr__(self):
rep = f'User:{self.id}:{self.username}' rep = f'User:{self.id}:{self.username}'
@ -1485,6 +1490,50 @@ class User(ObjectBase):
rep = f'User:{self.username}' rep = f'User:{self.username}'
return rep 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: class WarningBag:
def __init__(self): def __init__(self):

View file

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

View file

@ -186,7 +186,7 @@
<li>Filename: {{photo.basename}}</li> <li>Filename: {{photo.basename}}</li>
{% set author = photo.get_author() %} {% set author = photo.get_author() %}
{% if author is not none %} {% 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 %} {% endif %}
{% if photo.width %} {% if photo.width %}
<li>Dimensions: {{photo.width}}x{{photo.height}} px</li> <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="/albums">Browse albums</a>
<a class="nice_link" href="/bookmarks">Bookmarks</a> <a class="nice_link" href="/bookmarks">Bookmarks</a>
{% if session.user %} {% 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 %} {% else %}
<a class="nice_link" href="/login">Log in</a> <a class="nice_link" href="/login">Log in</a>
{% endif %} {% endif %}

View file

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

View file

@ -238,6 +238,25 @@ def upgrade_11_to_12(photodb):
''' '''
photodb.sql.cursor().execute(query) 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): def upgrade_all(data_directory):
''' '''
Given the directory containing a phototagger database, apply all of the Given the directory containing a phototagger database, apply all of the