More CreateNewCustomColumn stuff.

- Improved documentation
- Check column headings for duplicates
- Method to return the current column headings as a dict
- Improved exception handling
This commit is contained in:
Charles Haley 2022-01-02 12:59:30 +00:00
parent fbb5208aac
commit 9a95d8b0c2

View File

@ -676,24 +676,48 @@ class CreateNewCustomColumn(object):
""" """
Provide an API to create new custom columns. Provide an API to create new custom columns.
Use the create_column() method to open a dialog to create a new custom Usage:
column with given lookup_name, column_heading, datatype, and is_multiple. from calibre.gui2.preferences.create_custom_column import CreateNewCustomColumn
The lookup name must begin with a '#'. The datatype must be valid and creator = CreateNewCustomColumn(gui)
is_multiple must be valid for that datatype. The user cannot change if creator.must_restart():
the datatype. ...
else:
result = creator.create_column(....)
if result[0] == creator.Result.COLUMN_ADDED:
If generate_unused_lookup_name is False then the provided lookup_name must The parameter 'gui' passed when creating a class instance is the main
not already exist. If generate_unused_lookup_name is True then if necessary calibre gui (calibre.gui2.ui.get_gui())
the method will add the suffix '_n' to the provided lookup_name to allocate
an unused lookup_name, where 'n' is an integer. It could be that if a new Use the create_column(...) method to open a dialog to create a new custom
lookup name is generated then the user will be required to change the column with given lookup_name, column_heading, datatype, and is_multiple.
column heading to make it is unique. You can create as many columns as you wish with a single instance of the
CreateNewCustomColumn class. Subsequent class instances will refuse to
create columns until calibre is restarted, as will calibre Preferences.
The lookup name must begin with a '#'. All remaining characters must be
lower case letters, digits or underscores. The character after the '#' must
be a letter. The lookup name must not end with the suffix '_index'.
The datatype must be one of calibre's custom column types: 'bool',
'comments', 'composite', 'datetime', 'enumeration', 'float', 'int',
'rating', 'series', or 'text'. The datatype can't be changed in the dialog.
is_multiple tells calibre that the column contains multiple values -- is
tags-like. The value True is allowed only for 'composite' and 'text' types.
If generate_unused_lookup_name is False then the provided lookup_name and
column_heading must not already exist. If generate_unused_lookup_name is
True then if necessary the method will add the suffix '_n' to the provided
lookup_name to allocate an unused lookup_name, where 'n' is an integer.
The same processing is applied to column_heading to make it is unique, using
the same suffix used for the lookup name if possible. In either case the
user can change the column heading in the dialog.
Set freeze_lookup_name to False if you want to allow the user choose a Set freeze_lookup_name to False if you want to allow the user choose a
different lookup name. The user will not be allowed to choose the lookup different lookup name. The user will not be allowed to choose the lookup
name of an existing column. The provided lookup_name either must not exist name of an existing column. The provided lookup_name and column_heading
or generate_unused_lookup_name must be True, regardless of the value of either must not exist or generate_unused_lookup_name must be True,
freeze_lookup_name. regardless of the value of freeze_lookup_name.
The 'display' parameter is used to pass item- and type-specific information The 'display' parameter is used to pass item- and type-specific information
for the column. It is a dict. The easiest way to see the current values for for the column. It is a dict. The easiest way to see the current values for
@ -740,21 +764,14 @@ class CreateNewCustomColumn(object):
You or the user must restart calibre for the column(s) to be actually added. You or the user must restart calibre for the column(s) to be actually added.
Result.EXCEPTION_RAISED is returned if the create dialog raises an exception.
This can happen if the display contains illegal values, for example a string
where a boolean is required. The string is the exception text. Run calibre
in debug mode to see the entire traceback.
The method returns Result.MUST_RESTART if further calibre configuration has The method returns Result.MUST_RESTART if further calibre configuration has
been blocked. You can check for this situation in advance by calling been blocked. You can check for this situation in advance by calling
must_restart(). must_restart().
The parameter 'gui' passed when creating a class instance is the main
calibre gui (calibre.gui2.ui.get_gui())
Usage:
from calibre.gui2.preferences.create_custom_column import CreateNewCustomColumn
creator = CreateNewCustomColumn(gui)
if creator.must_restart():
...
else:
result = creator.create_column(....)
if result[0] == creator.Result.COLUMN_ADDED:
""" """
class Result(Enum): class Result(Enum):
@ -762,10 +779,12 @@ class CreateNewCustomColumn(object):
CANCELED = 1 CANCELED = 1
INVALID_KEY = 2 INVALID_KEY = 2
DUPLICATE_KEY = 3 DUPLICATE_KEY = 3
INVALID_TYPE = 4 DUPLICATE_HEADING = 4
INVALID_IS_MULTIPLE = 5 INVALID_TYPE = 5
INVALID_DISPLAY = 6 INVALID_IS_MULTIPLE = 6
MUST_RESTART = 7 INVALID_DISPLAY = 7
EXCEPTION_RAISED = 8
MUST_RESTART = 9
def __init__(self, gui): def __init__(self, gui):
self.gui = gui self.gui = gui
@ -783,14 +802,28 @@ class CreateNewCustomColumn(object):
return (self.Result.MUST_RESTART, _("You must restart calibre before making any more changes")) return (self.Result.MUST_RESTART, _("You must restart calibre before making any more changes"))
if not lookup_name.startswith('#'): if not lookup_name.startswith('#'):
return (self.Result.INVALID_KEY, _("The lookup name must begin with a '#'")) return (self.Result.INVALID_KEY, _("The lookup name must begin with a '#'"))
suffix_number = 1
if lookup_name in self.custcols: if lookup_name in self.custcols:
if not generate_unused_lookup_name: if not generate_unused_lookup_name:
return(self.Result.DUPLICATE_KEY, _("The custom column %s already exists") % lookup_name) return(self.Result.DUPLICATE_KEY, _("The custom column %s already exists") % lookup_name)
for i in range(1, 10000): for suffix_number in range(suffix_number, 100000):
nk = '%s_%d'%(lookup_name, i) nk = '%s_%d'%(lookup_name, suffix_number)
if nk not in self.custcols: if nk not in self.custcols:
lookup_name = nk lookup_name = nk
break break
if column_heading:
headings = {v['name'] for v in self.custcols.values()}
if column_heading in headings:
if not generate_unused_lookup_name:
return(self.Result.DUPLICATE_HEADING,
_("The column heading %s already exists") % column_heading)
for i in range(suffix_number, 100000):
nh = '%s_%d'%(column_heading, i)
if nh not in headings:
column_heading = nh
break
else:
column_heading = lookup_name
if datatype not in CreateCustomColumn.column_types_map: if datatype not in CreateCustomColumn.column_types_map:
return(self.Result.INVALID_TYPE, return(self.Result.INVALID_TYPE,
_("The custom column type %s doesn't exist") % datatype) _("The custom column type %s doesn't exist") % datatype)
@ -800,10 +833,6 @@ class CreateNewCustomColumn(object):
if not isinstance(display, dict): if not isinstance(display, dict):
return(self.Result.INVALID_DISPLAY, return(self.Result.INVALID_DISPLAY,
_("The display parameter must a python dict")) _("The display parameter must a python dict"))
if not column_heading:
column_heading = lookup_name
self.key = lookup_name
self.created_count += 1 self.created_count += 1
self.custcols[lookup_name] = { self.custcols[lookup_name] = {
'label': lookup_name, 'label': lookup_name,
@ -814,18 +843,26 @@ class CreateNewCustomColumn(object):
'colnum': self.created_count, 'colnum': self.created_count,
'is_multiple': is_multiple, 'is_multiple': is_multiple,
} }
dialog = CreateCustomColumn(self.gui, self, lookup_name, self.gui.library_view.model().orig_headers, try:
freeze_lookup_name=freeze_lookup_name) dialog = CreateCustomColumn(self.gui, self, lookup_name,
if dialog.result() == QDialog.DialogCode.Accepted and self.cc_column_key is not None: self.gui.library_view.model().orig_headers,
cc = self.custcols[lookup_name] freeze_lookup_name=freeze_lookup_name)
self.db.create_custom_column( if dialog.result() == QDialog.DialogCode.Accepted and self.cc_column_key is not None:
label=cc['label'], cc = self.custcols[lookup_name]
name=cc['name'], self.db.create_custom_column(
datatype=cc['datatype'], label=cc['label'],
is_multiple=cc['is_multiple'], name=cc['name'],
display=cc['display']) datatype=cc['datatype'],
self.gui.must_restart_before_config = True is_multiple=cc['is_multiple'],
return ((self.Result.COLUMN_ADDED, self.cc_column_key)) display=cc['display'])
self.gui.must_restart_before_config = True
return (self.Result.COLUMN_ADDED, self.cc_column_key)
except Exception as e:
import traceback
traceback.print_exc()
self.custcols.pop(lookup_name, None)
return (self.Result.EXCEPTION_RAISED, str(e))
self.custcols.pop(lookup_name, None)
return (self.Result.CANCELED, _('Canceled')) return (self.Result.CANCELED, _('Canceled'))
def current_columns(self): def current_columns(self):
@ -847,7 +884,21 @@ class CreateNewCustomColumn(object):
doesn't use. See calibre.library.field_metadata.add_custom_field() for the doesn't use. See calibre.library.field_metadata.add_custom_field() for the
complete list. complete list.
""" """
return copy.deepcopy(self.custcols) #deepcopy to prevent users from changing it # deepcopy to prevent users from changing it. The new MappingProxyType
# isn't enough because only the top-level dict is immutable, not the
# items in the dict.
return copy.deepcopy(self.custcols)
def current_headings(self):
"""
Return the currently defined column headings
Return the column headings including the ones that haven't yet been
created. It is a dict. The key is the heading, the value is the lookup
name having that heading.
"""
return {v['name']:('#' + v['label']) for v in self.custcols.values()}
def must_restart(self): def must_restart(self):
"""Return true if calibre must be restarted before new columns can be added.""" """Return true if calibre must be restarted before new columns can be added."""