Give Users a display_name.
This commit is contained in:
		
							parent
							
								
									8562b355ce
								
							
						
					
					
						commit
						21bd211889
					
				
					 9 changed files with 84 additions and 11 deletions
				
			
		|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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': [ | ||||||
|  |  | ||||||
|  | @ -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.' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
|     <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> | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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 %} | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue