diff --git a/bauble/__init__.py b/bauble/__init__.py index 46786521..ccc9b296 100755 --- a/bauble/__init__.py +++ b/bauble/__init__.py @@ -294,7 +294,7 @@ def main(uri=None): # break except Exception, e: msg = _("Could not open connection.\n\n%s") % \ - utils.xml_safe_utf8(e) + utils.xml_safe(repr(e)) utils.message_details_dialog(msg, traceback.format_exc(), gtk.MESSAGE_ERROR) uri = None diff --git a/bauble/db.py b/bauble/db.py index c1c36d90..a6c0ab6c 100644 --- a/bauble/db.py +++ b/bauble/db.py @@ -20,6 +20,7 @@ import logging logger = logging.getLogger(__name__) +#logger.setLevel(logging.DEBUG) from sqlalchemy.orm import class_mapper @@ -223,7 +224,7 @@ def open(uri, verify=True, show_error_dialogs=False): """ # ** WARNING: this can print your passwd -## debug('db.open(%s)' % uri) + logger.debug('db.open(%s)' % uri) from sqlalchemy.orm import sessionmaker global engine new_engine = None @@ -235,7 +236,14 @@ def open(uri, verify=True, show_error_dialogs=False): new_engine = sa.create_engine(uri, echo=SQLALCHEMY_DEBUG, implicit_returning=False, poolclass=pool.SingletonThreadPool) - new_engine.connect().close() # make sure we can connect + # TODO: there is a problem here: the code may cause an exception, but we + # immediately loose the 'new_engine', which should know about the + # encoding used in the exception string. + try: + new_engine.connect().close() # make sure we can connect + except Exception: + logger.warning('about to forget about encoding of exception text.') + raise def _bind(): """bind metadata to engine and create sessionmaker """ diff --git a/bauble/editor.py b/bauble/editor.py index dd02b5ea..8c50b625 100755 --- a/bauble/editor.py +++ b/bauble/editor.py @@ -29,6 +29,7 @@ import logging logger = logging.getLogger(__name__) +#logger.setLevel(logging.DEBUG) import glib import gtk @@ -401,14 +402,17 @@ def init_translatable_combo(self, combo, translations, default=None, self.assign_simple_handler() :param combo: - :param translations: a dictionary of values->translation + :param translations: a list of pairs, or a dictionary, + of values->translation. """ if isinstance(combo, basestring): combo = self.widgets[combo] combo.clear() # using 'object' avoids SA unicode warning model = gtk.ListStore(object, str) - for key, value in sorted(translations.iteritems(), key=lambda x: x[1]): + if isinstance(translations, dict): + translations = sorted(translations.iteritems(), key=lambda x: x[1]) + for key, value in translations: model.append([key, value]) combo.set_model(model) cell = gtk.CellRendererText() @@ -513,6 +517,8 @@ def remove_problem(self, problem_id, problem_widgets=None): from, if None then remove all occurrences of problem_id regardless of the widget """ + logger.debug('remove_problem(%s, %s, %s)' % + (self, problem_id, problem_widgets)) if problem_id is None and problem_widgets is None: logger.warning('invoke remove_problem with None, None') # if no problem id and not problem widgets then don't do anything @@ -1124,9 +1130,10 @@ def set_content(self, filename): pixbuf = fullbuf.scale_simple( x, y, gtk.gdk.INTERP_BILINEAR) im.set_from_pixbuf(pixbuf) - except glib.GError: + except glib.GError, e: + logger.debug("picture %s caused glib.GError %s" % + (filename, e)) label = _('picture file %s not found.') % filename - logger.debug(label) im = gtk.Label() im.set_text(label) except Exception, e: @@ -1161,11 +1168,13 @@ def on_activate_browse_button(self, widget, data=None): from PIL import Image im = Image.open(filename) im.thumbnail((400, 400)) - im.save(os.path.join( - prefs.prefs[prefs.picture_root_pref], 'thumbs', filename)) + self.last_folder, basename = os.path.split(filename) + full_dest_path = os.path.join( + prefs.prefs[prefs.picture_root_pref], 'thumbs', basename) + logger.debug('copying %s to %s' % (filename, full_dest_path)) + im.save(full_dest_path) ## get dirname and basename from selected file, memorize ## dirname - self.last_folder, basename = os.path.split(filename) ## make sure the category is self.set_model_attr('category', u'') ## store basename in note field and fire callbacks. diff --git a/bauble/plugins/garden/accession.py b/bauble/plugins/garden/accession.py index 1bb834e5..a2c41256 100755 --- a/bauble/plugins/garden/accession.py +++ b/bauble/plugins/garden/accession.py @@ -32,6 +32,7 @@ import logging logger = logging.getLogger(__name__) +#logger.setLevel(logging.DEBUG) import gtk @@ -42,7 +43,7 @@ from sqlalchemy import ForeignKey, Column, Unicode, Integer, Boolean, \ UnicodeText from sqlalchemy.orm import EXT_CONTINUE, MapperExtension, \ - backref, relation, reconstructor + backref, relation, reconstructor, validates from sqlalchemy.orm.session import object_session from sqlalchemy.exc import DBAPIError @@ -218,7 +219,7 @@ def acc_markup_func(acc): """ Returns str(acc), acc.species_str() """ - return utils.xml_safe_utf8(unicode(acc)), acc.species_str(markup=True) + return utils.xml_safe(unicode(acc)), acc.species_str(markup=True) # TODO: accession should have a one-to-many relationship on verifications @@ -351,20 +352,34 @@ def after_update(self, mapper, conn, instance): return EXT_CONTINUE -prov_type_values = {u'Wild': _('Wild'), - u'Cultivated': _('Propagule of cultivated wild plant'), - u'NotWild': _("Not of wild source"), - u'InsufficientData': _("Insufficient Data"), - u'Unknown': _("Unknown"), - None: ''} - - -wild_prov_status_values = {u'WildNative': _("Wild native"), - u'WildNonNative': _("Wild non-native"), - u'CultivatedNative': _("Cultivated native"), - u'InsufficientData': _("Insufficient Data"), - u'Unknown': _("Unknown"), - None: ''} +prov_type_values = [(u'Wild', _('Wild')), + (u'NotWild', _("Not of wild source")), + (u'Cultivated', _('Propagule of cultivated (wild) plant')), + (u'Purchase', _('Purchase or gift')), + (u'InsufficientData', _("Insufficient Data")), + (u'Unknown', _("Unknown")), + (None, '')] + +wild_prov_status_values = [(u'WildNative', _("Wild native")), + (u'WildNonNative', _("Wild non-native")), + (u'CultivatedNative', _("Cultivated native")), + (u'Impound', _("Impound")), + (u'Collection', _("Collection")), + (u'Rescue', _("Rescue")), + (u'InsufficientData', _("Insufficient Data")), + (u'Unknown', _("Unknown")), + (None, '')] + +purchase_prov_status_values = [(u'National', _("National")), + (u'Imported', _("Imported")), + (u'Unknown', _("Unknown")), + (None, '')] + +cultivated_prov_status_values = [(u'InVitro', _("In vitro")), + (u'Division', _("Division")), + (u'Seed', _("Seed")), + (u'Unknown', _("Unknown")), + (None, '')] recvd_type_values = { @@ -456,22 +471,17 @@ class Accession(db.Base, db.Serializable): the provenance type Possible values: - * Wild: - * Propagule of cultivated wild plant - * Not of wild source - * Insufficient Data - * Unknown + * first column of prov_type_values *wild_prov_status*: :class:`bauble.types.Enum` - wild provenance status, if prov_type is - Wild then this column can be used to give more provenance + this column can be used to give more provenance information Possible values: - * Wild native - * Cultivated native - * Insufficient Data - * Unknown + * union of first columns of + wild_prov_status_values, + purchase_prov_status_values, + cultivated_prov_status_values *date*: :class:`bauble.types.Date` the date this accession was accessioned @@ -523,13 +533,20 @@ class Accession(db.Base, db.Serializable): #: the accession code code = Column(Unicode(20), nullable=False, unique=True) - prov_type = Column(types.Enum(values=prov_type_values.keys(), - translations=prov_type_values), + @validates('code') + def validate_stripping(self, key, value): + if value is None: + return None + return value.strip() + + prov_type = Column(types.Enum(values=[i[0] for i in prov_type_values], + translations=dict(prov_type_values)), default=None) - wild_prov_status = Column(types.Enum(values=wild_prov_status_values.keys(), - translations=wild_prov_status_values), - default=None) + wild_prov_status = Column( + types.Enum(values=[i[0] for i in wild_prov_status_values], + translations=dict(wild_prov_status_values)), + default=None) date_accd = Column(types.Date) date_recvd = Column(types.Date) @@ -753,10 +770,11 @@ class AccessionEditorView(editor.GenericEditorView): 'acc_prov_combo': (_('The origin or source of this accession.\n\n' 'Possible values: %s') % - ', '.join(prov_type_values.values())), + ', '.join(i[1] for i in prov_type_values)), 'acc_wild_prov_combo': (_('The wild status is used to clarify the ' 'provenance.\n\nPossible values: %s') % - ', '.join(wild_prov_status_values.values())), + ', '.join(i[1] + for i in wild_prov_status_values)), 'acc_private_check': _('Indicates whether this accession record ' 'should be considered private.'), 'acc_cancel_button': _('Cancel your changes.'), @@ -1014,23 +1032,6 @@ def dirty(self): def refresh_view(self): pass - def on_taxon_add_button_clicked(self, button, taxon_entry): - ## the `model` is an accession, it does refer to a species, but we - ## come here when we are adding a Verification, so we are not - ## interested in editing anything, we just want to add a taxon. - - ## we really should check what happened in the SpeciesEditor, - ## because we do want to immediately use the object added by the - ## user! - - from bauble.plugins.plants.species import SpeciesEditor - editor = SpeciesEditor(parent=self.view.get_window()) - if editor.start(): - self.session.add(editor.model) - taxon_entry.set_text("%s" % editor.model) - self.remove_problem(None, taxon_entry) - self.parent_ref().refresh_sensitivity() - def on_add_clicked(self, *args): self.add_verification_box() @@ -1051,7 +1052,6 @@ def __init__(self, parent, model): super(VerificationPresenter.VerificationBox, self).__init__(self) check(not model or isinstance(model, Verification)) - self.dirty = False self.presenter = weakref.ref(parent) self.model = model if not self.model: @@ -1144,7 +1144,7 @@ def on_sp_select(value): ## add a taxon implies setting the ver_new_taxon_entry self.presenter().view.connect( self.widgets.ver_taxon_add_button, 'clicked', - self.presenter().on_taxon_add_button_clicked, + self.on_taxon_add_button_clicked, ver_new_taxon_entry) combo = self.widgets.ver_level_combo @@ -1232,21 +1232,20 @@ def on_level_combo_changed(self, combo, *args): def set_model_attr(self, attr, value): setattr(self.model, attr, value) if attr != 'date' and not self.model.date: - # this is a little voodoo to set the date on the model - # since when we create a new verification box we add - # today's date to the entry but we don't set the model - # so the presenter doesn't appear dirty...we have to - # use a tmp variable since the changed signal won't - # fire if the new value is the same as the old + # When we create a new verification box we set today's date + # in the GtkEntry but not in the model so the presenter + # doesn't appear dirty. Now that the user is setting + # something, we trigger the 'changed' signal on the 'date' + # entry as well, by first clearing the entry then setting it + # to its intended value. tmp = self.date_entry.props.text self.date_entry.props.text = '' self.date_entry.props.text = tmp - # if the verification is new and isn't yet associated - # with an accession then set the accession when we - # start changing values, this way we can setup a dummy - # verification in the interface - if not self.model.accession: - self.presenter().model.verifications.append(self.model) + # if the verification isn't yet associated with an accession + # then set the accession when we start changing values, this way + # we can setup a dummy verification in the interface + if not self.model.accession: + self.presenter().model.verifications.append(self.model) self.presenter()._dirty = True self.update_label() self.presenter().parent_ref().refresh_sensitivity() @@ -1257,9 +1256,9 @@ def update_label(self): if self.model.date: parts.append('%(date)s : ') if self.model.species: - parts.append('verified as %(species)s ') + parts.append(_('verified as %(species)s ')) if self.model.verifier: - parts.append('by %(verifier)s') + parts.append(_('by %(verifier)s')) label = ' '.join(parts) % dict(date=self.model.date, species=self.model.species, verifier=self.model.verifier) @@ -1269,6 +1268,23 @@ def update_label(self): def set_expanded(self, expanded): self.widgets.ver_expander.props.expanded = expanded + def on_taxon_add_button_clicked(self, button, taxon_entry): + ## we come here when we are adding a Verification, and the + ## Verification wants to refer to a new taxon. + + from bauble.plugins.plants.species import SpeciesEditor + editor = SpeciesEditor(parent=self.presenter().view.get_window()) + if editor.start(): + logger.debug('new taxon added from within VerificationBox') + # add the new taxon to the session and start using it + self.presenter().session.add(editor.model) + taxon_entry.set_text("%s" % editor.model) + self.presenter().remove_problem( + hash(taxon_entry.get_name()), None) + self.set_model_attr('species', editor.model) + logger.debug('is VerificationPresenter dirty? %s' % + self.presenter()._dirty) + class SourcePresenter(editor.GenericEditorPresenter): """ @@ -1414,6 +1430,7 @@ def dirty(self): self.collection_presenter.dirty() def refresh_sensitivity(self): + logger.warning('refresh_sensitivity: %s' % str(self.problems)) self.parent_ref().refresh_sensitivity() def on_coll_add_button_clicked(self, *args): @@ -2041,15 +2058,18 @@ def refresh_view(self): value = getattr(self.model, field) self.view.set_widget_value(widget, value) - self.view.set_widget_value('acc_wild_prov_combo', - wild_prov_status_values[self.model.wild_prov_status], - index=1) - self.view.set_widget_value('acc_prov_combo', - prov_type_values[self.model.prov_type], - index=1) - self.view.set_widget_value('acc_recvd_type_comboentry', - recvd_type_values[self.model.recvd_type], - index=1) + self.view.set_widget_value( + 'acc_wild_prov_combo', + dict(wild_prov_status_values)[self.model.wild_prov_status], + index=1) + self.view.set_widget_value( + 'acc_prov_combo', + dict(prov_type_values)[self.model.prov_type], + index=1) + self.view.set_widget_value( + 'acc_recvd_type_comboentry', + recvd_type_values[self.model.recvd_type], + index=1) self.view.widgets.acc_private_check.set_inconsistent(False) self.view.widgets.acc_private_check.\ @@ -2350,10 +2370,10 @@ def update(self, row): quantity_str = row.quantity_recvd self.set_widget_value('quantity_recvd_data', quantity_str) - prov_str = prov_type_values[row.prov_type] + prov_str = dict(prov_type_values)[row.prov_type] if row.prov_type == u'Wild' and row.wild_prov_status: prov_str = '%s (%s)' % \ - (prov_str, wild_prov_status_values[row.wild_prov_status]) + (prov_str, dict(wild_prov_status_values)[row.wild_prov_status]) self.set_widget_value('prov_data', prov_str, False) image_size = gtk.ICON_SIZE_MENU diff --git a/bauble/plugins/garden/location.py b/bauble/plugins/garden/location.py index 2e0482dc..d9e5a703 100755 --- a/bauble/plugins/garden/location.py +++ b/bauble/plugins/garden/location.py @@ -25,7 +25,7 @@ import gtk from sqlalchemy import Column, Unicode, UnicodeText -from sqlalchemy.orm import relation, backref +from sqlalchemy.orm import relation, backref, validates from sqlalchemy.orm.session import object_session from sqlalchemy.exc import DBAPIError @@ -121,6 +121,12 @@ class Location(db.Base, db.Serializable): # relations plants = relation('Plant', backref=backref('location', uselist=False)) + @validates('code', 'name') + def validate_stripping(self, key, value): + if value is None: + return None + return value.strip() + def __str__(self): if self.name: return '(%s) %s' % (self.code, self.name) diff --git a/bauble/plugins/garden/plant.py b/bauble/plugins/garden/plant.py index d6befa9d..4bdafe98 100755 --- a/bauble/plugins/garden/plant.py +++ b/bauble/plugins/garden/plant.py @@ -38,7 +38,7 @@ from sqlalchemy import and_, func from sqlalchemy import ForeignKey, Column, Unicode, Integer, Boolean, \ UnicodeText, UniqueConstraint -from sqlalchemy.orm import relation, backref, object_mapper +from sqlalchemy.orm import relation, backref, object_mapper, validates from sqlalchemy.orm.session import object_session from sqlalchemy.exc import DBAPIError @@ -59,8 +59,6 @@ select_in_search_results, Action import bauble.view as view -from bauble import pictures_view - # TODO: do a magic attribute on plant_id that checks if a plant id # already exists with the accession number, this probably won't work # though sense the acc_id may not be set when setting the plant_id @@ -419,6 +417,13 @@ class Plant(db.Base, db.Serializable): # columns code = Column(Unicode(6), nullable=False) + + @validates('code') + def validate_stripping(self, key, value): + if value is None: + return None + return value.strip() + acc_type = Column(types.Enum(values=acc_type_values.keys(), translations=acc_type_values), default=None) @@ -458,13 +463,15 @@ def pictures(self): y = int(pixbuf.get_height() / scale) scaled_buf = pixbuf.scale_simple(x, y, gtk.gdk.INTERP_BILINEAR) im.set_from_pixbuf(scaled_buf) - except glib.GError: + except glib.GError, e: + logger.debug("picture %s caused glib.GError %s" % + (filename, e)) label = _('picture file %s not found.') % filename - logger.debug(label) im = gtk.Label() im.set_text(label) except Exception, e: - logger.warning(e) + logger.warning("picture %s caused Exception %s" % + (filename, e)) im = gtk.Label() im.set_text(_("%s" % e)) result.append(im) diff --git a/bauble/plugins/garden/propagation.py b/bauble/plugins/garden/propagation.py index 56524ab6..1199a262 100644 --- a/bauble/plugins/garden/propagation.py +++ b/bauble/plugins/garden/propagation.py @@ -1,5 +1,23 @@ # -*- coding: utf-8 -*- # +# Copyright 2008-2010 Brett Adams +# Copyright 2015 Mario Frasca . +# +# This file is part of bauble.classic. +# +# bauble.classic is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# bauble.classic is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with bauble.classic. If not, see . +# # propagation module # @@ -53,7 +71,7 @@ class Propagation(db.Base): """ __tablename__ = 'propagation' #recvd_as = Column(Unicode(10)) # seed, urcu, other - #recvd_as_other = Column(UnicodeText) # ** maybe this should be in the notes + #recvd_as_other = Column(UnicodeText) # maybe this should be in the notes prop_type = Column(types.Enum(values=prop_type_values.keys(), translations=prop_type_values), nullable=False) @@ -124,9 +142,10 @@ def get_date(date): if c.hormone: values.append(_('Hormone treatment: %s' % c.hormone)) if c.bottom_heat_temp: - values.append(_('Bottom heat: %(temp)s%(unit)s') % - dict(temp=c.bottom_heat_temp, - unit=bottom_heat_unit_values[c.bottom_heat_unit])) + values.append( + _('Bottom heat: %(temp)s%(unit)s') % + dict(temp=c.bottom_heat_temp, + unit=bottom_heat_unit_values[c.bottom_heat_unit])) if c.container: values.append(_('Container: %s' % c.container)) if c.media: @@ -309,14 +328,12 @@ class PropSeed(db.Base): propagation_id = Column(Integer, ForeignKey('propagation.id'), nullable=False) - def __str__(self): # what would the string be...??? # cuttings of self.accession.species_str() and accession number return repr(self) - class PropagationTabPresenter(editor.GenericEditorPresenter): """PropagationTabPresenter @@ -342,11 +359,9 @@ def __init__(self, parent, model, view, session): tab_box.pack_start(box, expand=False, fill=True) self.__dirty = False - def dirty(self): return self.__dirty - def add_propagation(self): """ Open the PropagationEditor and append the resulting @@ -367,7 +382,6 @@ def add_propagation(self): else: propagation.plant = None - def create_propagation_box(self, propagation): """ """ @@ -429,7 +443,6 @@ def on_remove_clicked(button, propagation, box): hbox.show_all() return hbox - def on_add_button_clicked(self, *args): """ """ @@ -437,7 +450,6 @@ def on_add_button_clicked(self, *args): self.parent_ref().refresh_sensitivity() - class PropagationEditorView(editor.GenericEditorView): """ """ @@ -453,17 +465,14 @@ def __init__(self, parent=None): parent=parent) self.init_translatable_combo('prop_type_combo', prop_type_values) - def get_window(self): """ """ return self.widgets.prop_dialog - def start(self): return self.get_window().run() - # TODO: if you edit an existing cutting and the the OK is not set sensitive # TODO: if you reopen an accession editor the list of propagations @@ -599,19 +608,15 @@ def _rooted_data_func(column, cell, model, treeiter, prop): self.view.connect('rooted_remove_button', "clicked", self.on_rooted_remove_clicked) - - def dirty(self): return self.__dirty - def set_model_attr(self, field, value, validator=None): #debug('%s = %s' % (field, value)) super(CuttingPresenter, self).set_model_attr(field, value, validator) self.__dirty = True self.parent_ref().refresh_sensitivity() - def on_rooted_cell_edited(self, cell, path, new_text, prop): treemodel = self.view.widgets.rooted_treeview.get_model() rooted = treemodel[path][0] @@ -621,7 +626,6 @@ def on_rooted_cell_edited(self, cell, path, new_text, prop): self.__dirty = True self.parent_ref().refresh_sensitivity() - def on_rooted_add_clicked(self, button, *args): """ """ @@ -635,7 +639,6 @@ def on_rooted_add_clicked(self, button, *args): column = tree.get_column(0) tree.set_cursor(path, column, start_editing=True) - def on_rooted_remove_clicked(self, button, *args): """ """ @@ -649,7 +652,6 @@ def on_rooted_remove_clicked(self, button, *args): self.__dirty = True self.parent_ref().refresh_sensitivity() - def refresh_view(self): for widget, attr in self.widget_to_field_map.iteritems(): value = getattr(self.model, attr) @@ -657,7 +659,6 @@ def refresh_view(self): self.view.set_widget_value(widget, value) - class SeedPresenter(editor.GenericEditorPresenter): widget_to_field_map = {'seed_pretreatment_textview': 'pretreatment', @@ -885,7 +886,7 @@ def __init__(self, parent, model, view, session): # only add the propagation editor widgets to the view # widgets if the widgets haven't yet been added filename = os.path.join(paths.lib_dir(), 'plugins', 'garden', - 'prop_editor.glade') + 'prop_editor.glade') view.widgets.builder.add_from_file(filename) prop_main_box = view.widgets.prop_main_box view.widgets.remove_parent(prop_main_box) @@ -910,7 +911,6 @@ def __init__(self, parent, model, view, session): self._dirty = False super(SourcePropagationPresenter, self).__init__(model, view) - def on_prop_type_changed(self, combo, *args): """ Override PropagationPresenter.on_type_changed() to handle the @@ -922,31 +922,26 @@ def on_prop_type_changed(self, combo, *args): prop_type = combo.get_model()[it][0] if not prop_type: self.set_model_attr('prop_type', None) - self.view.widgets.prop_details_box.props.visible=False + self.view.widgets.prop_details_box.props.visible = False else: super(SourcePropagationPresenter, self).\ on_prop_type_changed(combo, *args) - def set_model_attr(self, attr, value, validator=None): #debug('set_model_attr(%s, %s)' % (attr, value)) super(SourcePropagationPresenter, self).set_model_attr(attr, value) self._dirty = True self.refresh_sensitivity() - def refresh_sensitivity(self): self.parent_ref().refresh_sensitivity() - def dirty(self): return super(SourcePropagationPresenter, self).dirty() or self._dirty - class PropagationEditorPresenter(PropagationPresenter): - def __init__(self, model, view): ''' :param model: an instance of class Propagation @@ -962,12 +957,10 @@ def __init__(self, model, view): self.view.widgets.prop_details_box.props.visible = False self.view.widgets.prop_ok_button.props.sensitive = False - def start(self): r = self.view.start() return r - def refresh_sensitivity(self): super(PropagationEditorPresenter, self).refresh_sensitivity() sensitive = True @@ -982,7 +975,8 @@ def refresh_sensitivity(self): model = self.model._seed if model: - invalid = utils.get_invalid_columns(model, ['id', 'propagation_id']) + invalid = utils.get_invalid_columns( + model, ['id', 'propagation_id']) # TODO: highlight the widget with are associated with the # columns that have bad values if invalid: @@ -1079,8 +1073,8 @@ def handle_response(self, response, commit=True): return False except Exception, e: msg = _('Unknown error when committing changes. See the ' - 'details for more information.\n\n%s') \ - % utils.xml_safe_utf8(e) + 'details for more information.\n\n%s') %\ + utils.xml_safe_utf8(e) logger.debug(traceback.format_exc()) utils.message_details_dialog(msg, traceback.format_exc(), gtk.MESSAGE_ERROR) diff --git a/bauble/plugins/garden/source.py b/bauble/plugins/garden/source.py index 71bc6648..e15ec75c 100755 --- a/bauble/plugins/garden/source.py +++ b/bauble/plugins/garden/source.py @@ -1,3 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008-2010 Brett Adams +# Copyright 2015 Mario Frasca . +# +# This file is part of bauble.classic. +# +# bauble.classic is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# bauble.classic is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with bauble.classic. If not, see . +# # # source.py # @@ -25,7 +45,6 @@ import bauble.btypes as types import bauble.view as view import bauble.paths as paths -#from bauble.plugins.garden.propagation import * def coll_markup_func(coll): @@ -136,19 +155,19 @@ class Source(db.Base): primaryjoin='Source.plant_propagation_id==Propagation.id') -source_type_values = {u'Expedition': _('Expedition'), - u'GeneBank': _('Gene Bank'), - u'BG': _('Botanic Garden or Arboretum'), - u'Research/FieldStation': _('Research/Field Station'), - u'Staff': _('Staff member'), - u'UniversityDepartment': _('University Department'), - u'Club': _('Horticultural Association/Garden Club'), - u'MunicipalDepartment': _('Municipal department'), - u'Commercial': _('Nursery/Commercial'), - u'Individual': _('Individual'), - u'Other': _('Other'), - u'Unknown': _('Unknown'), - None: ''} +source_type_values = [(u'Expedition', _('Expedition')), + (u'GeneBank', _('Gene Bank')), + (u'BG', _('Botanic Garden or Arboretum')), + (u'Research/FieldStation', _('Research/Field Station')), + (u'Staff', _('Staff member')), + (u'UniversityDepartment', _('University Department')), + (u'Club', _('Horticultural Association/Garden Club')), + (u'MunicipalDepartment', _('Municipal department')), + (u'Commercial', _('Nursery/Commercial')), + (u'Individual', _('Individual')), + (u'Other', _('Other')), + (u'Unknown', _('Unknown')), + (None, '')] class SourceDetail(db.Base): @@ -157,8 +176,8 @@ class SourceDetail(db.Base): name = Column(Unicode(75), unique=True) description = Column(UnicodeText) - source_type = Column(types.Enum(values=source_type_values.keys(), - translations=source_type_values), + source_type = Column(types.Enum(values=[i[0] for i in source_type_values], + translations=dict(source_type_values)), default=None) def __str__(self): @@ -248,7 +267,8 @@ def __init__(self, parent=None): 'acc_editor.glade') super(SourceDetailEditorView, self).__init__(filename, parent=parent) self.set_accept_buttons_sensitive(False) - self.init_translatable_combo('source_type_combo', source_type_values) + self.init_translatable_combo( + 'source_type_combo', source_type_values) def get_window(self): return self.widgets.source_details_dialog @@ -297,9 +317,10 @@ def refresh_view(self): (widget, field, getattr(self.model, field))) self.view.set_widget_value(widget, getattr(self.model, field)) - self.view.set_widget_value('source_type_combo', - source_type_values[self.model.source_type], - index=1) + self.view.set_widget_value( + 'source_type_combo', + dict(source_type_values)[self.model.source_type], + index=1) def start(self): r = self.view.start() diff --git a/bauble/plugins/plants/family.py b/bauble/plugins/plants/family.py index dfaca5ae..5531896f 100755 --- a/bauble/plugins/plants/family.py +++ b/bauble/plugins/plants/family.py @@ -28,7 +28,7 @@ from sqlalchemy import Column, Unicode, Integer, ForeignKey, \ UnicodeText, func, and_, UniqueConstraint, String -from sqlalchemy.orm import relation, backref, class_mapper +from sqlalchemy.orm import relation, backref, validates from sqlalchemy.orm.session import object_session from sqlalchemy.exc import DBAPIError from sqlalchemy.ext.associationproxy import association_proxy @@ -150,6 +150,12 @@ class Family(db.Base, db.Serializable): rank = 'familia' + @validates('genus') + def validate_stripping(self, key, value): + if value is None: + return None + return value.strip() + @property def cites(self): '''the cites status of this taxon, or None diff --git a/bauble/plugins/plants/genus.py b/bauble/plugins/plants/genus.py index 28eb07ee..97de40e5 100755 --- a/bauble/plugins/plants/genus.py +++ b/bauble/plugins/plants/genus.py @@ -30,7 +30,7 @@ from sqlalchemy import Column, Unicode, Integer, ForeignKey, \ UnicodeText, func, and_, UniqueConstraint, String -from sqlalchemy.orm import relation, backref +from sqlalchemy.orm import relation, backref, validates from sqlalchemy.orm.session import object_session from sqlalchemy.exc import DBAPIError from sqlalchemy.ext.associationproxy import association_proxy @@ -181,6 +181,13 @@ def cites(self): # use '' instead of None so that the constraints will work propertly author = Column(Unicode(255), default=u'') + + @validates('genus', 'author') + def validate_stripping(self, key, value): + if value is None: + return None + return value.strip() + qualifier = Column(types.Enum(values=['s. lat.', 's. str', u'']), default=u'') diff --git a/bauble/utils/__init__.py b/bauble/utils/__init__.py index 9cbaf41c..3d103ced 100755 --- a/bauble/utils/__init__.py +++ b/bauble/utils/__init__.py @@ -1,6 +1,24 @@ +# Copyright (c) 2005,2006,2007,2008,2009 Brett Adams +# Copyright (c) 2015 Mario Frasca +# +# This file is part of bauble.classic. +# +# bauble.classic is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# bauble.classic is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with bauble.classic. If not, see . # # utils module # + """ A common set of utility functions used throughout Bauble. """ @@ -20,6 +38,7 @@ import logging logger = logging.getLogger(__name__) +#logger.setLevel(logging.DEBUG) def find_dependent_tables(table, metadata=None): @@ -646,6 +665,8 @@ def to_unicode(obj, encoding='utf-8'): object it will not try to decode it to converted it to but will just return the original obj """ + logger.debug('utils.to_unicode((%s)%s, "%s")' % + (type(obj), obj, encoding)) if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) @@ -678,6 +699,8 @@ def xml_safe_utf8(obj): """ This method is deprecated and just returns xml_safe(obj) """ + logger.warning('invoking deprecated function') + return xml_safe(obj) diff --git a/bauble/version.py b/bauble/version.py index 1bc6d7c3..92f7bb59 100644 --- a/bauble/version.py +++ b/bauble/version.py @@ -16,4 +16,4 @@ # The Bauble version. # major, minor, revision version tuple -version = "1.0.31" # :bump +version = "1.0.32" # :bump diff --git a/data/bauble.desktop b/data/bauble.desktop index a2373d63..adac3579 100644 --- a/data/bauble.desktop +++ b/data/bauble.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Encoding=UTF-8 Name=Bauble -Version=1.0.31 # :bump +Version=1.0.32 # :bump Comment=An application for managing botanical collections Terminal=False Icon=bauble diff --git a/packages/builddeb.sh b/packages/builddeb.sh index 4ecdd401..2e11995f 100755 --- a/packages/builddeb.sh +++ b/packages/builddeb.sh @@ -3,7 +3,7 @@ # Requires bzr, devscripts, debhelper packages TOPLEVEL=`pwd` -VERSION="1.0.31" # :bump +VERSION="1.0.32" # :bump TARBALL="bauble-$VERSION.tar.gz" ORIG_TARBALL="bauble_$VERSION.orig.tar.gz" diff --git a/scripts/build.nsi b/scripts/build.nsi index a249ae9a..d2865609 100755 --- a/scripts/build.nsi +++ b/scripts/build.nsi @@ -12,7 +12,7 @@ ; general Name "Bauble" -!define version "1.0.31" ; :bump +!define version "1.0.32" ; :bump !define src_dir "../dist" Outfile "bauble-${version}-setup.exe" diff --git a/scripts/devinstall.bat b/scripts/devinstall.bat index a7413d54..d1734e81 100644 --- a/scripts/devinstall.bat +++ b/scripts/devinstall.bat @@ -7,16 +7,31 @@ GOTO CONTINUE set CHECKOUT=bauble-1.0 :CONTINUE -echo going to install %CHECKOUT% + +ECHO going to install %CHECKOUT% cd "%HOMEDRIVE%%HOMEPATH%" + +ECHO installing dependencies pip install virtualenv 2>NUL virtualenv --system-site-packages .virtualenvs\bacl + +ECHO clearing previous checkouts +for /F "delims=" %%i in ( + 'dir /b .virtualenvs\bacl\Lib\site-packages\bauble-*egg' +) do ( + rmdir ".virtualenvs\bacl\Lib\site-packages\""%%i" /s/q +) + +ECHO going to checkout %CHECKOUT% call .virtualenvs\bacl\Scripts\activate.bat mkdir Local\github\Bauble 2>NUL cd Local\github\Bauble git clone https://github.com/Bauble/bauble.classic.git cd bauble.classic git checkout %CHECKOUT% + +ECHO going to build and install python setup.py build python setup.py install -mkdir "%APPDATA%\Bauble" +mkdir "%APPDATA%\Bauble" 2>NUL +cd "%HOMEPATH%"