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.
Use the create_column() method to open a dialog to create a new custom
column with given lookup_name, column_heading, datatype, and is_multiple.
The lookup name must begin with a '#'. The datatype must be valid and
is_multiple must be valid for that datatype. The user cannot change
the datatype.
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:
If generate_unused_lookup_name is False then the provided lookup_name 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. It could be that if a new
lookup name is generated then the user will be required to change the
column heading to make it is unique.
The parameter 'gui' passed when creating a class instance is the main
calibre gui (calibre.gui2.ui.get_gui())
Use the create_column(...) method to open a dialog to create a new custom
column with given lookup_name, column_heading, datatype, and is_multiple.
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
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
or generate_unused_lookup_name must be True, regardless of the value of
freeze_lookup_name.
name of an existing column. The provided lookup_name and column_heading
either must not exist or generate_unused_lookup_name must be True,
regardless of the value of freeze_lookup_name.
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
@ -740,21 +764,14 @@ class CreateNewCustomColumn(object):
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
been blocked. You can check for this situation in advance by calling
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):
@ -762,10 +779,12 @@ class CreateNewCustomColumn(object):
CANCELED = 1
INVALID_KEY = 2
DUPLICATE_KEY = 3
INVALID_TYPE = 4
INVALID_IS_MULTIPLE = 5
INVALID_DISPLAY = 6
MUST_RESTART = 7
DUPLICATE_HEADING = 4
INVALID_TYPE = 5
INVALID_IS_MULTIPLE = 6
INVALID_DISPLAY = 7
EXCEPTION_RAISED = 8
MUST_RESTART = 9
def __init__(self, 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"))
if not lookup_name.startswith('#'):
return (self.Result.INVALID_KEY, _("The lookup name must begin with a '#'"))
suffix_number = 1
if lookup_name in self.custcols:
if not generate_unused_lookup_name:
return(self.Result.DUPLICATE_KEY, _("The custom column %s already exists") % lookup_name)
for i in range(1, 10000):
nk = '%s_%d'%(lookup_name, i)
for suffix_number in range(suffix_number, 100000):
nk = '%s_%d'%(lookup_name, suffix_number)
if nk not in self.custcols:
lookup_name = nk
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:
return(self.Result.INVALID_TYPE,
_("The custom column type %s doesn't exist") % datatype)
@ -800,10 +833,6 @@ class CreateNewCustomColumn(object):
if not isinstance(display, dict):
return(self.Result.INVALID_DISPLAY,
_("The display parameter must a python dict"))
if not column_heading:
column_heading = lookup_name
self.key = lookup_name
self.created_count += 1
self.custcols[lookup_name] = {
'label': lookup_name,
@ -814,18 +843,26 @@ class CreateNewCustomColumn(object):
'colnum': self.created_count,
'is_multiple': is_multiple,
}
dialog = CreateCustomColumn(self.gui, self, lookup_name, self.gui.library_view.model().orig_headers,
freeze_lookup_name=freeze_lookup_name)
if dialog.result() == QDialog.DialogCode.Accepted and self.cc_column_key is not None:
cc = self.custcols[lookup_name]
self.db.create_custom_column(
label=cc['label'],
name=cc['name'],
datatype=cc['datatype'],
is_multiple=cc['is_multiple'],
display=cc['display'])
self.gui.must_restart_before_config = True
return ((self.Result.COLUMN_ADDED, self.cc_column_key))
try:
dialog = CreateCustomColumn(self.gui, self, lookup_name,
self.gui.library_view.model().orig_headers,
freeze_lookup_name=freeze_lookup_name)
if dialog.result() == QDialog.DialogCode.Accepted and self.cc_column_key is not None:
cc = self.custcols[lookup_name]
self.db.create_custom_column(
label=cc['label'],
name=cc['name'],
datatype=cc['datatype'],
is_multiple=cc['is_multiple'],
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'))
def current_columns(self):
@ -847,7 +884,21 @@ class CreateNewCustomColumn(object):
doesn't use. See calibre.library.field_metadata.add_custom_field() for the
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):
"""Return true if calibre must be restarted before new columns can be added."""