From 21bd211889cf5151d447ef6ae7249b93689194f9 Mon Sep 17 00:00:00 2001 From: Ethan Dalool Date: Sun, 15 Apr 2018 14:23:24 -0700 Subject: [PATCH] Give Users a display_name. --- README.md | 1 - etiquette/constants.py | 7 ++- etiquette/exceptions.py | 3 ++ etiquette/objects.py | 51 ++++++++++++++++++- .../etiquette_flask/templates/header.html | 4 +- .../etiquette_flask/templates/photo.html | 2 +- frontends/etiquette_flask/templates/root.html | 2 +- frontends/etiquette_flask/templates/user.html | 6 +-- .../database_upgrader/database_upgrader.py | 19 +++++++ 9 files changed, 84 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e53536e..19b9d96 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/etiquette/constants.py b/etiquette/constants.py index 1949e98..0a250bc 100644 --- a/etiquette/constants.py +++ b/etiquette/constants.py @@ -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': [ diff --git a/etiquette/exceptions.py b/etiquette/exceptions.py index 68c04ac..b070a69 100644 --- a/etiquette/exceptions.py +++ b/etiquette/exceptions.py @@ -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.' diff --git a/etiquette/objects.py b/etiquette/objects.py index 6048db4..cfaacfa 100644 --- a/etiquette/objects.py +++ b/etiquette/objects.py @@ -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): diff --git a/frontends/etiquette_flask/templates/header.html b/frontends/etiquette_flask/templates/header.html index a4cf7b0..f13286f 100644 --- a/frontends/etiquette_flask/templates/header.html +++ b/frontends/etiquette_flask/templates/header.html @@ -4,10 +4,10 @@ Search Tags {% if session.user %} - {{session.user.username}} + {{session.user.display_name}} Logout {% else %} Log in {% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/frontends/etiquette_flask/templates/photo.html b/frontends/etiquette_flask/templates/photo.html index 74154f4..205a908 100644 --- a/frontends/etiquette_flask/templates/photo.html +++ b/frontends/etiquette_flask/templates/photo.html @@ -186,7 +186,7 @@
  • Filename: {{photo.basename}}
  • {% set author = photo.get_author() %} {% if author is not none %} -
  • Author: {{author.username}}
  • +
  • Author: {{author.display_name}}
  • {% endif %} {% if photo.width %}
  • Dimensions: {{photo.width}}x{{photo.height}} px
  • diff --git a/frontends/etiquette_flask/templates/root.html b/frontends/etiquette_flask/templates/root.html index 281473f..6c81227 100644 --- a/frontends/etiquette_flask/templates/root.html +++ b/frontends/etiquette_flask/templates/root.html @@ -44,7 +44,7 @@ body, .nice_link Browse albums Bookmarks {% if session.user %} - {{session.user.username}} + {{session.user.display_name}} {% else %} Log in {% endif %} diff --git a/frontends/etiquette_flask/templates/user.html b/frontends/etiquette_flask/templates/user.html index be211b6..8db8ac5 100644 --- a/frontends/etiquette_flask/templates/user.html +++ b/frontends/etiquette_flask/templates/user.html @@ -2,7 +2,7 @@ {% import "header.html" as header %} - User {{user.username}} + User {{user.display_name}} @@ -20,9 +20,9 @@ {{header.make_header(session=session)}}
    -

    {{user.username}}

    +

    {{user.display_name}}

    ID: {{user.id}}

    -

    Photos by {{user.username}}

    +

    Photos by {{user.display_name}}

    diff --git a/utilities/database_upgrader/database_upgrader.py b/utilities/database_upgrader/database_upgrader.py index 32f6c8e..3955be2 100644 --- a/utilities/database_upgrader/database_upgrader.py +++ b/utilities/database_upgrader/database_upgrader.py @@ -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