From b0bc3616ef16c7a8eb1284cb9b9a6dbba57e2784 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Thu, 11 Jun 2015 12:40:52 -0600 Subject: [PATCH 001/158] remove services and models from odmtools, begin to use api --- odmtools/odmdata/__init__.py | 12 +- odmtools/odmdata/base.py | 2 - odmtools/odmdata/censor_code_cv.py | 11 - odmtools/odmdata/data_type_cv.py | 11 - odmtools/odmdata/data_value.py | 74 -- odmtools/odmdata/general_category_cv.py | 11 - odmtools/odmdata/iso_metadata.py | 15 - odmtools/odmdata/lab_method.py | 15 - odmtools/odmdata/method.py | 12 - odmtools/odmdata/odm_version.py | 10 - odmtools/odmdata/offset_type.py | 17 - odmtools/odmdata/qualifier.py | 12 - odmtools/odmdata/quality_control_level.py | 13 - odmtools/odmdata/sample.py | 18 - odmtools/odmdata/sample_medium_cv.py | 11 - odmtools/odmdata/sample_type_cv.py | 11 - odmtools/odmdata/series.py | 129 ---- odmtools/odmdata/session_factory.py | 57 -- odmtools/odmdata/site.py | 37 - odmtools/odmdata/site_type_cv.py | 11 - odmtools/odmdata/source.py | 27 - odmtools/odmdata/spatial_reference.py | 14 - odmtools/odmdata/speciation_cv.py | 11 - odmtools/odmdata/topic_category_cv.py | 11 - odmtools/odmdata/unit.py | 13 - odmtools/odmdata/value_type_cv.py | 11 - odmtools/odmdata/variable.py | 28 - odmtools/odmdata/variable_name_cv.py | 11 - odmtools/odmdata/vertical_datum_cv.py | 11 - odmtools/odmservices/__init__.py | 18 - odmtools/odmservices/cv_service.py | 100 --- odmtools/odmservices/edit_service.py | 583 ---------------- odmtools/odmservices/export_service.py | 401 ----------- odmtools/odmservices/series_service.py | 782 ---------------------- odmtools/odmservices/service_manager.py | 216 ------ 35 files changed, 5 insertions(+), 2721 deletions(-) delete mode 100644 odmtools/odmdata/base.py delete mode 100644 odmtools/odmdata/censor_code_cv.py delete mode 100644 odmtools/odmdata/data_type_cv.py delete mode 100644 odmtools/odmdata/data_value.py delete mode 100644 odmtools/odmdata/general_category_cv.py delete mode 100644 odmtools/odmdata/iso_metadata.py delete mode 100644 odmtools/odmdata/lab_method.py delete mode 100644 odmtools/odmdata/method.py delete mode 100644 odmtools/odmdata/odm_version.py delete mode 100644 odmtools/odmdata/offset_type.py delete mode 100644 odmtools/odmdata/qualifier.py delete mode 100644 odmtools/odmdata/quality_control_level.py delete mode 100644 odmtools/odmdata/sample.py delete mode 100644 odmtools/odmdata/sample_medium_cv.py delete mode 100644 odmtools/odmdata/sample_type_cv.py delete mode 100644 odmtools/odmdata/series.py delete mode 100644 odmtools/odmdata/session_factory.py delete mode 100644 odmtools/odmdata/site.py delete mode 100644 odmtools/odmdata/site_type_cv.py delete mode 100644 odmtools/odmdata/source.py delete mode 100644 odmtools/odmdata/spatial_reference.py delete mode 100644 odmtools/odmdata/speciation_cv.py delete mode 100644 odmtools/odmdata/topic_category_cv.py delete mode 100644 odmtools/odmdata/unit.py delete mode 100644 odmtools/odmdata/value_type_cv.py delete mode 100644 odmtools/odmdata/variable.py delete mode 100644 odmtools/odmdata/variable_name_cv.py delete mode 100644 odmtools/odmdata/vertical_datum_cv.py delete mode 100644 odmtools/odmservices/__init__.py delete mode 100644 odmtools/odmservices/cv_service.py delete mode 100644 odmtools/odmservices/edit_service.py delete mode 100644 odmtools/odmservices/export_service.py delete mode 100644 odmtools/odmservices/series_service.py delete mode 100755 odmtools/odmservices/service_manager.py diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index aaab1ef..9e7c314 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,3 +1,4 @@ +''' from base import Base from censor_code_cv import CensorCodeCV from data_type_cv import DataTypeCV @@ -26,7 +27,6 @@ from variable import Variable from variable_name_cv import VariableNameCV from vertical_datum_cv import VerticalDatumCV -from memory_database import MemoryDatabase from series import copy_series from data_value import copy_data_value @@ -64,11 +64,9 @@ 'copy_series', 'copy_data_value' ] +''' -# pyinstaller needs pymysql, psycopg2, and pyodbc (If on windows) import sys -if sys.platform is not 'darwin': - import pymysql - import psycopg2 - import pyodbc - +sys.path.append('/Users/stephanie/DEV/ODM2PythonAPI/') +import src.api as api +from src.api.ODMconnection import SessionFactory \ No newline at end of file diff --git a/odmtools/odmdata/base.py b/odmtools/odmdata/base.py deleted file mode 100644 index c1da040..0000000 --- a/odmtools/odmdata/base.py +++ /dev/null @@ -1,2 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base -Base = declarative_base() \ No newline at end of file diff --git a/odmtools/odmdata/censor_code_cv.py b/odmtools/odmdata/censor_code_cv.py deleted file mode 100644 index 254a825..0000000 --- a/odmtools/odmdata/censor_code_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class CensorCodeCV(Base): - __tablename__ = 'CensorCodeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/data_type_cv.py b/odmtools/odmdata/data_type_cv.py deleted file mode 100644 index da23bfe..0000000 --- a/odmtools/odmdata/data_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class DataTypeCV(Base): - __tablename__ = 'DataTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/data_value.py b/odmtools/odmdata/data_value.py deleted file mode 100644 index 687414a..0000000 --- a/odmtools/odmdata/data_value.py +++ /dev/null @@ -1,74 +0,0 @@ -# Declare a mapped class -from sqlalchemy import Column, Integer, Float, DateTime, ForeignKey, String -from sqlalchemy.orm import relationship -from base import Base -from site import Site -from variable import Variable -from qualifier import Qualifier -from method import Method -from source import Source -from quality_control_level import QualityControlLevel -from offset_type import OffsetType -from sample import Sample - -def copy_data_value(from_dv): - new = DataValue() - new.data_value = from_dv.data_value - new.value_accuracy = from_dv.value_accuracy - new.local_date_time = from_dv.local_date_time - new.utc_offset = from_dv.utc_offset - new.date_time_utc = from_dv.date_time_utc - new.site_id = from_dv.site_id - new.variable_id = from_dv.variable_id - new.offset_value = from_dv.offset_value - new.offset_type_id = from_dv.offset_type_id - new.censor_code = from_dv.censor_code - new.qualifier_id = from_dv.qualifier_id - new.method_id = from_dv.method_id - new.source_id = from_dv.source_id - new.sample_id = from_dv.sample_id - new.derived_from_id = from_dv.derived_from_id - new.quality_control_level_id = from_dv.quality_control_level_id - return new - -class DataValue(Base): - __tablename__ = 'DataValues' - - id = Column('ValueID', Integer, primary_key=True) - data_value = Column('DataValue', Float) - value_accuracy = Column('ValueAccuracy', Float) - local_date_time = Column('LocalDateTime', DateTime) - utc_offset = Column('UTCOffset', Float) - date_time_utc = Column('DateTimeUTC', DateTime) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - offset_value = Column('OffsetValue', Float) - offset_type_id = Column('OffsetTypeID', Integer, ForeignKey('OffsetTypes.OffsetTypeID')) - censor_code = Column('CensorCode', String) - qualifier_id = Column('QualifierID', Integer, ForeignKey('Qualifiers.QualifierID')) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - sample_id = Column('SampleID', Integer, ForeignKey('Samples.SampleID')) - derived_from_id = Column('DerivedFromID', Integer) - quality_control_level_id = Column('QualityControlLevelID', Integer, ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - - # relationships - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - qualifier = relationship(Qualifier) - offset_type = relationship(OffsetType) - sample = relationship(Sample) - - def list_repr(self): - return [self.id, self.data_value, self.value_accuracy, self.local_date_time, - self.utc_offset, self.date_time_utc, self.site_id, self.variable_id, - self.offset_value, self.offset_type_id, self.censor_code, self.qualifier_id, - self.method_id, self.source_id, self.sample_id, self.derived_from_id, - self.quality_control_level_id] - - def __repr__(self): - return "" % (self.data_value, self.local_date_time, self.value_accuracy) diff --git a/odmtools/odmdata/general_category_cv.py b/odmtools/odmdata/general_category_cv.py deleted file mode 100644 index e721166..0000000 --- a/odmtools/odmdata/general_category_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class GeneralCategoryCV(Base): - __tablename__ = 'GeneralCategoryCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/iso_metadata.py b/odmtools/odmdata/iso_metadata.py deleted file mode 100644 index cd090d4..0000000 --- a/odmtools/odmdata/iso_metadata.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, Integer, String -from base import Base - -class ISOMetadata(Base): - __tablename__ = 'ISOMetadata' - - id = Column('MetadataID', Integer, primary_key=True) - topic_category = Column('TopicCategory', String, nullable=False) - title = Column('Title', String, nullable=False) - abstract = Column('Abstract', String, nullable=False) - profile_version = Column('ProfileVersion', String, nullable=False) - metadata_link = Column('MetadataLink', String) - - def __repr__(self): - return "" % (self.id, self.topic_category, self.title) \ No newline at end of file diff --git a/odmtools/odmdata/lab_method.py b/odmtools/odmdata/lab_method.py deleted file mode 100644 index 1b933cb..0000000 --- a/odmtools/odmdata/lab_method.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class LabMethod(Base): - __tablename__ = 'LabMethods' - - id = Column('LabMethodID', Integer, primary_key=True) - name = Column('LabName', String, nullable=False) - organization = Column('LabOrganization', String, nullable=False) - method_name = Column('LabMethodName', String, nullable=False) - method_description = Column('LabMethodDescription', String, nullable=False) - method_link = Column('LabMethodLink', String) - - def __repr__(self): - return "" % (self.id, self.name, self.organization, self.method_name) \ No newline at end of file diff --git a/odmtools/odmdata/method.py b/odmtools/odmdata/method.py deleted file mode 100644 index f3b9d8f..0000000 --- a/odmtools/odmdata/method.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Column, Integer, String -from base import Base - -class Method(Base): - __tablename__ = 'Methods' - - id = Column('MethodID', Integer, primary_key=True) - description = Column('MethodDescription', String, nullable=False) - link = Column('MethodLink', String) - - def __repr__(self): - return "" % (self.id, self.description, self.link) \ No newline at end of file diff --git a/odmtools/odmdata/odm_version.py b/odmtools/odmdata/odm_version.py deleted file mode 100644 index 3fe4420..0000000 --- a/odmtools/odmdata/odm_version.py +++ /dev/null @@ -1,10 +0,0 @@ -from sqlalchemy import String, Column -from base import Base - -class ODMVersion(Base): - __tablename__ = 'ODMVersion' - - version_number = Column('VersionNumber', String, primary_key=True) - - def __repr__(self): - return "" % (self.version_number) \ No newline at end of file diff --git a/odmtools/odmdata/offset_type.py b/odmtools/odmdata/offset_type.py deleted file mode 100644 index f1fc6ca..0000000 --- a/odmtools/odmdata/offset_type.py +++ /dev/null @@ -1,17 +0,0 @@ -from sqlalchemy import Column, Integer, ForeignKey, String -from sqlalchemy.orm import relationship -from base import Base -from unit import Unit - -class OffsetType(Base): - __tablename__ = 'OffsetTypes' - - id = Column('OffsetTypeID', Integer, primary_key=True) - unit_id = Column('OffsetUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - description = Column('OffsetDescription', String) - - # relationships - unit = relationship(Unit) - - def __repr__(self): - return "" % (self.id, self.unit_id, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/qualifier.py b/odmtools/odmdata/qualifier.py deleted file mode 100644 index 052febb..0000000 --- a/odmtools/odmdata/qualifier.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class Qualifier(Base): - __tablename__ = 'Qualifiers' - - id = Column('QualifierID', Integer, primary_key=True) - code = Column('QualifierCode', String, nullable=False) - description = Column('QualifierDescription', String, nullable=False) - - def __repr__(self): - return "" % (self.id, self.code, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/quality_control_level.py b/odmtools/odmdata/quality_control_level.py deleted file mode 100644 index 5e3c226..0000000 --- a/odmtools/odmdata/quality_control_level.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class QualityControlLevel(Base): - __tablename__ = 'QualityControlLevels' - - id = Column('QualityControlLevelID', Integer, primary_key=True) - code = Column('QualityControlLevelCode', String, nullable=False) - definition = Column('Definition', String, nullable=False) - explanation = Column('Explanation', String, nullable=False) - - def __repr__(self): - return "" % (self.id, self.code, self.definition, self.explanation) \ No newline at end of file diff --git a/odmtools/odmdata/sample.py b/odmtools/odmdata/sample.py deleted file mode 100644 index 9d18b33..0000000 --- a/odmtools/odmdata/sample.py +++ /dev/null @@ -1,18 +0,0 @@ -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from lab_method import LabMethod - -class Sample(Base): - __tablename__ = 'Samples' - - id = Column('SampleID', Integer, primary_key=True) - type = Column('SampleType', String, nullable=False) - lab_sample_code = Column('LabSampleCode', String, nullable=False) - lab_method_id = Column('LabMethodID', Integer, ForeignKey('LabMethods.LabMethodID'), nullable=False) - - # relationships - lab_method = relationship(LabMethod) - - def __repr__(self): - return "" % (self.id, self.type, self.lab_sample_code, self.lab_method_id) \ No newline at end of file diff --git a/odmtools/odmdata/sample_medium_cv.py b/odmtools/odmdata/sample_medium_cv.py deleted file mode 100644 index 277f2bd..0000000 --- a/odmtools/odmdata/sample_medium_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SampleMediumCV(Base): - __tablename__ = 'SampleMediumCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/sample_type_cv.py b/odmtools/odmdata/sample_type_cv.py deleted file mode 100644 index caaf4c0..0000000 --- a/odmtools/odmdata/sample_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SampleTypeCV(Base): - __tablename__ = 'SampleTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py deleted file mode 100644 index 3916544..0000000 --- a/odmtools/odmdata/series.py +++ /dev/null @@ -1,129 +0,0 @@ -from collections import OrderedDict # Requires Python 2.7 >= - -from sqlalchemy import Column, Integer, ForeignKey, String, Float, DateTime -from sqlalchemy.orm import relationship - -from base import Base -from site import Site -from variable import Variable -from method import Method -from source import Source -from quality_control_level import QualityControlLevel - -def copy_series(from_series): - new = Series() - new.site_id = from_series.site_id - new.site_code = from_series.site_code - new.site_name = from_series.site_name - new.variable_id = from_series.variable_id - new.variable_code = from_series.variable_code - new.variable_name = from_series.variable_name - new.speciation = from_series.speciation - new.variable_units_id = from_series.variable_units_id - new.variable_units_name = from_series.variable_units_name - new.sample_medium = from_series.sample_medium - new.value_type = from_series.value_type - new.time_support = from_series.time_support - new.time_units_id = from_series.time_units_id - new.time_units_name = from_series.time_units_name - new.data_type = from_series.data_type - new.general_category = from_series.general_category - new.method_id = from_series.method_id - new.method_description = from_series.method_description - new.source_id = from_series.source_id - new.source_description = from_series.source_description - new.organization = from_series.organization - new.citation = from_series.citation - new.quality_control_level_id = from_series.quality_control_level_id - new.quality_control_level_code = from_series.quality_control_level_code - new.begin_date_time = from_series.begin_date_time - new.begin_date_time_utc = from_series.begin_date_time_utc - new.end_date_time_utc = from_series.end_date_time_utc - new.value_count = from_series.value_count - return new -class Series(Base): - __tablename__ = 'seriescatalog' - - id = Column('SeriesID', Integer, primary_key=True) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - site_code = Column('SiteCode', String) - site_name = Column('SiteName', String) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - variable_code = Column('VariableCode', String) - variable_name = Column('VariableName', String) - speciation = Column('Speciation', String) - variable_units_id = Column('VariableUnitsID', Integer) - variable_units_name = Column('VariableUnitsName', String) - sample_medium = Column('SampleMedium', String) - value_type = Column('ValueType', String) - time_support = Column('TimeSupport', Float) - time_units_id = Column('TimeUnitsID', Integer) - time_units_name = Column('TimeUnitsName', String) - data_type = Column('DataType', String) - general_category = Column('GeneralCategory', String) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - method_description = Column('MethodDescription', String) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - source_description = Column('SourceDescription', String) - organization = Column('Organization', String) - citation = Column('Citation', String) - quality_control_level_id = Column('QualityControlLevelID', Integer, - ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - quality_control_level_code = Column('QualityControlLevelCode', String) - begin_date_time = Column('BeginDateTime', DateTime) - end_date_time = Column('EndDateTime', DateTime) - begin_date_time_utc = Column('BeginDateTimeUTC', DateTime) - end_date_time_utc = Column('EndDateTimeUTC', DateTime) - value_count = Column('ValueCount', Integer) - - data_values = relationship("DataValue", - primaryjoin="and_(DataValue.site_id == Series.site_id, " - "DataValue.variable_id == Series.variable_id, " - "DataValue.method_id == Series.method_id, " - "DataValue.source_id == Series.source_id, " - "DataValue.quality_control_level_id == Series.quality_control_level_id)", - foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", - order_by="DataValue.local_date_time", - backref="series") - - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - # TODO add all to repr - def __repr__(self): - return "" % (self.id, self.site_name, self.variable_code, self.variable_name) - - - def get_table_columns(self): - return self.__table__.columns.keys() - - def list_repr(self): - return [self.id, self.site_id, self.site_code, self.site_name, self.variable_id, self.variable_code, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.quality_control_level_code, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] - -def returnDict(): - keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) - - - diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py deleted file mode 100644 index 0d2d488..0000000 --- a/odmtools/odmdata/session_factory.py +++ /dev/null @@ -1,57 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - - -class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_size=20) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - - ''' - # Removing pool_timeout and max_overflow allowed the tests to pass - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, pool_size=20, max_overflow=0) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - ''' - - ''' - # Old code - class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, - #pool_size=20, - pool_recycle=3600) - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - ''' - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) - self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) - self.my_test_Session = sessionmaker(bind=self.my_test_engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - diff --git a/odmtools/odmdata/site.py b/odmtools/odmdata/site.py deleted file mode 100644 index c92998a..0000000 --- a/odmtools/odmdata/site.py +++ /dev/null @@ -1,37 +0,0 @@ -# Declare a mapped class -from sqlalchemy import Column, Integer, String, Float, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from spatial_reference import SpatialReference - -class Site(Base): - __tablename__ = 'Sites' - - id = Column('SiteID', Integer, primary_key=True) - code = Column('SiteCode', String) - name = Column('SiteName', String) - latitude = Column('Latitude', Float) - longitude = Column('Longitude', Float) - lat_long_datum_id = Column('LatLongDatumID', Integer, ForeignKey('SpatialReferences.SpatialReferenceID')) - elevation_m = Column('Elevation_m', Float) - vertical_datum_id = Column('VerticalDatum', Integer) - local_x = Column('LocalX', Float) - local_y = Column('LocalY', Float) - local_projection_id = Column('LocalProjectionID', Integer, ForeignKey('SpatialReferences.SpatialReferenceID')) - pos_accuracy_m = Column('PosAccuracy_m', Float) - state = Column('State', String) - county = Column('County', String) - comments = Column('Comments', String) - - type = Column('SiteType', String) - - # relationships - spatial_ref = relationship(SpatialReference, primaryjoin=("SpatialReference.id==Site.lat_long_datum_id")) - local_spatial_ref = relationship(SpatialReference, primaryjoin=("SpatialReference.id==Site.local_projection_id")) - - def __init__(self, site_code, site_name): - self.code = site_code - self.name = site_name - - def __repr__(self): - return "" % (self.code, self.name) \ No newline at end of file diff --git a/odmtools/odmdata/site_type_cv.py b/odmtools/odmdata/site_type_cv.py deleted file mode 100644 index fc41c7e..0000000 --- a/odmtools/odmdata/site_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SiteTypeCV(Base): - __tablename__ = 'SiteTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/source.py b/odmtools/odmdata/source.py deleted file mode 100644 index 08452cc..0000000 --- a/odmtools/odmdata/source.py +++ /dev/null @@ -1,27 +0,0 @@ -from sqlalchemy import Column, String, Integer, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from iso_metadata import ISOMetadata - -class Source(Base): - __tablename__ = 'Sources' - - id = Column('SourceID', Integer, primary_key=True) - organization = Column('Organization', String, nullable=False) - description = Column('SourceDescription', String, nullable=False) - link = Column('SourceLink', String) - contact_name = Column('ContactName', String, nullable=False) - phone = Column('Phone', String, nullable=False) - email = Column('Email', String, nullable=False) - address = Column('Address', String, nullable=False) - city = Column('City', String, nullable=False) - state = Column('State', String, nullable=False) - zip_code = Column('ZipCode', String, nullable=False) - citation = Column('Citation', String, nullable=False) - iso_metadata_id = Column('MetadataID', Integer, ForeignKey('ISOMetadata.MetadataID'), nullable=False) - - # relationships - iso_metadata = relationship(ISOMetadata) - - def __repr__(self): - return "" % (self.id, self.organization, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/spatial_reference.py b/odmtools/odmdata/spatial_reference.py deleted file mode 100644 index 2b63f39..0000000 --- a/odmtools/odmdata/spatial_reference.py +++ /dev/null @@ -1,14 +0,0 @@ -from sqlalchemy import Column, Integer, String, Boolean -from base import Base - -class SpatialReference(Base): - __tablename__ = 'SpatialReferences' - - id = Column('SpatialReferenceID', Integer, primary_key=True) - srs_id = Column('SRSID', Integer) - srs_name = Column('SRSName', String) - is_geographic = Column('IsGeographic', Boolean) - notes = Column('Notes', String) - - def __repr__(self): - return "" % (self.id, self.srs_name) \ No newline at end of file diff --git a/odmtools/odmdata/speciation_cv.py b/odmtools/odmdata/speciation_cv.py deleted file mode 100644 index 7d6c19f..0000000 --- a/odmtools/odmdata/speciation_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SpeciationCV(Base): - __tablename__ = 'SpeciationCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/topic_category_cv.py b/odmtools/odmdata/topic_category_cv.py deleted file mode 100644 index 54f65c5..0000000 --- a/odmtools/odmdata/topic_category_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class TopicCategoryCV(Base): - __tablename__ = 'TopicCategoryCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/unit.py b/odmtools/odmdata/unit.py deleted file mode 100644 index 853da31..0000000 --- a/odmtools/odmdata/unit.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import Column, Integer, String, Unicode -from base import Base - -class Unit(Base): - __tablename__ = 'Units' - - id = Column('UnitsID', Integer, primary_key=True) - name = Column('UnitsName', String) - type = Column('UnitsType', String) - abbreviation = Column('UnitsAbbreviation', String)#(convert_unicode=True)) - - def __repr__(self): - return "" % (self.id, self.name, self.type) \ No newline at end of file diff --git a/odmtools/odmdata/value_type_cv.py b/odmtools/odmdata/value_type_cv.py deleted file mode 100644 index 1a19d8a..0000000 --- a/odmtools/odmdata/value_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class ValueTypeCV(Base): - __tablename__ = 'ValueTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/variable.py b/odmtools/odmdata/variable.py deleted file mode 100644 index 77d8109..0000000 --- a/odmtools/odmdata/variable.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Float -from sqlalchemy.orm import relationship -from base import Base -from unit import Unit - -class Variable(Base): - __tablename__ = 'Variables' - - id = Column('VariableID', Integer, primary_key=True) - code = Column('VariableCode', String, nullable=False) - name = Column('VariableName', String, nullable=False) - speciation = Column('Speciation', String, nullable=False) - variable_unit_id = Column('VariableUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - sample_medium = Column('SampleMedium', String, nullable=False) - value_type = Column('ValueType', String, nullable=False) - is_regular = Column('IsRegular', Boolean, nullable=False) - time_support = Column('TimeSupport', Float, nullable=False) - time_unit_id = Column('TimeUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - data_type = Column('DataType', String, nullable=False) - general_category = Column('GeneralCategory', String, nullable=False) - no_data_value = Column('NoDataValue', Float, nullable=False) - - # relationships - variable_unit = relationship(Unit, primaryjoin=("Unit.id==Variable.variable_unit_id")) # <-- Uses class attribute names, not table column names - time_unit = relationship(Unit, primaryjoin=("Unit.id==Variable.time_unit_id")) - - def __repr__(self): - return "" % (self.id, self.code, self.name) \ No newline at end of file diff --git a/odmtools/odmdata/variable_name_cv.py b/odmtools/odmdata/variable_name_cv.py deleted file mode 100644 index 0b64596..0000000 --- a/odmtools/odmdata/variable_name_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class VariableNameCV(Base): - __tablename__ = 'VariableNameCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/vertical_datum_cv.py b/odmtools/odmdata/vertical_datum_cv.py deleted file mode 100644 index b161b29..0000000 --- a/odmtools/odmdata/vertical_datum_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class VerticalDatumCV(Base): - __tablename__ = 'VerticalDatumCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py deleted file mode 100644 index 3952589..0000000 --- a/odmtools/odmservices/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from service_manager import ServiceManager -from series_service import SeriesService -from cv_service import CVService -from edit_service import EditService -from export_service import ExportService - -# need to explicitly import these for pyinstaller -import pymysql -import pyodbc -import psycopg2 - -__all__ = [ - 'EditService', - 'CVService', - 'SeriesService', - 'ExportService', - 'ServiceManager', -] \ No newline at end of file diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py deleted file mode 100644 index 0e92b7c..0000000 --- a/odmtools/odmservices/cv_service.py +++ /dev/null @@ -1,100 +0,0 @@ -# CV imports -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import VerticalDatumCV -from odmtools.odmdata import SiteTypeCV -from odmtools.odmdata import VariableNameCV -from odmtools.odmdata import SpeciationCV -from odmtools.odmdata import SampleMediumCV -from odmtools.odmdata import ValueTypeCV -from odmtools.odmdata import DataTypeCV -from odmtools.odmdata import GeneralCategoryCV -from odmtools.odmdata import CensorCodeCV -from odmtools.odmdata import TopicCategoryCV -from odmtools.odmdata import SampleTypeCV -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Qualifier -from odmtools.odmdata import Unit -from sqlalchemy import not_ - - -class CVService(): - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug - - # Controlled Vocabulary get methods - - #return a list of all terms in the cv - def get_vertical_datum_cvs(self): - result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() - return result - - def get_samples(self): - result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() - return result - - - - def get_site_type_cvs(self): - result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() - return result - - def get_variable_name_cvs(self): - result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() - return result - - def get_offset_type_cvs(self): - result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() - return result - - def get_speciation_cvs(self): - result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() - return result - - def get_sample_medium_cvs(self): - result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() - return result - - def get_value_type_cvs(self): - result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() - return result - - def get_data_type_cvs(self): - result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() - return result - - def get_general_category_cvs(self): - result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() - return result - - def get_censor_code_cvs(self): - result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() - return result - - def get_sample_type_cvs(self): - result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() - return result - - def get_units(self): - result = self._edit_session.query(Unit).all() - return result - - def get_units_not_uni(self): - result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() - return result - - def get_units_names(self): - result = self._edit_session.query(Unit.name).all() - return result - - # return a single cv - def get_unit_by_name(self, unit_name): - result = self._edit_session.query(Unit).filter_by(name=unit_name).first() - return result - - def get_unit_by_id(self, unit_id): - result = self._edit_session.query(Unit).filter_by(id=unit_id).first() - return result diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py deleted file mode 100644 index ccc1f76..0000000 --- a/odmtools/odmservices/edit_service.py +++ /dev/null @@ -1,583 +0,0 @@ -import sqlite3 - - -from odmtools.odmdata import DataValue -from series_service import SeriesService - -from odmtools.odmdata import series as series_module - -import pandas as pd -import datetime -import numpy as np - -import logging -from odmtools.common.logger import LoggerTool - -tool = LoggerTool() -logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) - - -class EditService(): - # Mutual exclusion: cursor, or connection_string - def __init__(self, series_id, connection=None, connection_string="", debug=False): - ''' - - :param series_id: - :param connection: memory database, contains connection to remote database - :param connection_string: connection to remote database - :param debug: - :return: - ''' - - self._series_id = series_id - self._filter_from_selection = False - self._debug = debug - - if connection_string is "" and connection is not None: - self.memDB= connection - #self._series_service = self.memDB.series_service#SeriesService(connection_string, debug) - - elif connection_string is not "" and connection is None: - from odmtools.odmdata import MemoryDatabase - self.memDB= MemoryDatabase()#(series_service) - self.memDB.set_series_service(SeriesService(connection_string, False)) - - - else: - logger.error("must send in either a remote db connection string or a memory database object") - - logger.debug("Initializing Memory Database") - self.memDB.initEditValues(series_id) - logger.debug("Finished Initializing Memory Database") - self._populate_series() - self.reset_filter() - - def get_series_service(self): - return self.memDB.series_service - - def _populate_series(self): - # [(ID, value, datetime), ...] - #self._cursor.execute("SELECT ValueID, DataValue, LocalDateTime FROM DataValues ORDER BY LocalDateTime") - - self._series_points_df = self.memDB.getDataValuesDF() - - - def _test_filter_previous(self): - - ''' - if not self._filter_from_selection: - self.reset_filter() - ''' - - df = None - - if not self._filter_from_selection: - df = self._series_points_df - else: - df = self.filtered_dataframe - - # Ensure that we're not working with an empty dataframe - - if isinstance(df, pd.DataFrame): - if df.empty: - return self._series_points_df - else: - if not df: - return self._series_points_df - - return df - - def datetime2dataframe(self, datetime_list): - """ Converts datetime_list to a pandas Dataframe - - - :param datetime_list: - :return Pandas.DataFrame: - """ - - result = None - - if isinstance(datetime_list, list): - - result = pd.DataFrame(datetime_list, columns=["LocalDateTime"]) - - result.set_index("LocalDateTime", inplace=True) - - return result - - ################### - # Stubs - ################### - def selectPointsStub(self): - """ - :param filtered_dataframe: - :return: - """ - - ## Convert dataframe into list of datetimes - - filtered_dataframe = self.get_filtered_points() - if isinstance(filtered_dataframe, pd.DataFrame): - if not filtered_dataframe.empty: - datetime_list = filtered_dataframe.index.to_pydatetime() - return datetime_list.tolist() - return [] - - ################### - # Filters - ################### - # operator is a character, either '<' or '>' - def filter_value(self, value, ops): - df = self._test_filter_previous() - - if ops == '>': - self.filtered_dataframe = df[df['DataValue'] > value] - - if ops == '<': - self.filtered_dataframe = df[df['DataValue'] < value] - - - def filter_date(self, before, after): - df = self._test_filter_previous() - if before and after: - self.filtered_dataframe = df[(df.index < before) & (df.index > after)] - - # Data Gaps - def data_gaps(self, value, time_period): - df = self._test_filter_previous() - - time_units = { - 'second': 's', - 'minute': 'm', - 'hour': 'h', - 'day': 'D', - 'week': 'W', - 'month': 'M', - 'year': 'Y' - } - - # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps - copy_df = df - copy_df['datetime'] = df.index - copy_df['dateprev'] = copy_df['datetime'].shift() - - # ensure that 'value' is an integer - if not isinstance(value, int): - value = int(value) - - # create a bool column indicating which rows meet condition - filtered_results = copy_df['datetime'].diff() >= np.timedelta64(value, time_units[time_period]) - - # filter on rows that passed previous condition - copy_df = copy_df[filtered_results] - - # merge values and remove duplicates. this hack allows for both values to be marked when selecting data gaps - newdf = pd.concat([copy_df['datetime'], copy_df['dateprev']], join='inner') - self.filtered_dataframe = df[df.index.isin(newdf.drop_duplicates().dropna())] - - # clean up - del copy_df - del filtered_results - del newdf - - def change_value_threshold(self, value, operator): - - df = self._test_filter_previous() - - # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps - copy_df = df - copy_df['values'] = df['DataValue'] - copy_df['diff'] = copy_df['values'].shift() - copy_df["diff_date"] = copy_df['LocalDateTime'].shift() - copy_df['change_threshold'] = abs(df['values'] - df['diff']) - - if not isinstance(value, float): - logger.error("Need to have a float") - return - - copy_df['threshold'] = value - - if operator == ">": - copy_df['matches'] = df['change_threshold'] >= copy_df['threshold'] - - if operator == "<": - copy_df['matches'] = df['change_threshold'] <= copy_df['threshold'] - - filtered_df = copy_df[copy_df['matches']] - tmplist = filtered_df['diff_date'].tolist() + filtered_df.index.tolist() - del copy_df - self.filtered_dataframe = df[df.index.isin(tmplist)] - - - def select_points_tf(self, tf_list): - self._filter_list = tf_list - - #def select_points(self, id_list=[], datetime_list=[]): - def select_points(self, id_list=[], dataframe=[]): - #self.reset_filter() - - # This should be either one or the other. If it's both, id is used first. - # If neither are set this function does nothing. - - if len(id_list) > 0: - for i in range(len(self._series_points)): - if self._series_points[i][0] in id_list: - self._filter_list[i] = True - - if isinstance(dataframe, pd.DataFrame): - result = dataframe.index.astype(datetime.datetime) - self.filtered_dataframe = self._series_points_df[self._series_points_df.index.isin(dataframe.index)] - - - def reset_filter(self): - self.filtered_dataframe = None - - def filter_from_previous(self, value): - self._filter_from_selection = value - - def get_toggle(self): - return self._filter_from_selection - - - ################### - # Gets - ################### - def get_series(self): - return self.memDB.series_service.get_series_by_id(self._series_id) - - def get_series_points(self): - # all point in the series - return self._series_points - - def get_series_points_df(self): - """ - :return Pandas DataFrame: - """ - return self._series_points_df - - def get_filtered_points(self): - """ - :return Pandas DataFrame: - """ - if isinstance(self.filtered_dataframe, pd.DataFrame): - if self.filtered_dataframe.empty: - return None - else: - if not self.filtered_dataframe: - return None - if len(self.filtered_dataframe) > 0: - return self.filtered_dataframe - return None - - def get_filtered_dates(self): - return self.filtered_dataframe - - def get_filter_list(self): - # true or false list the length of the entire series. true indicate the point is selected - return self._filter_list - - def get_qcl(self, qcl_id): - return self.memDB.series_service.get_qcl_by_id(qcl_id) - - def get_method(self, method_id): - return self.memDB.series_service.get_method_by_id(method_id) - - def get_variable(self, variable_id): - logger.debug(variable_id) - return self.memDB.series_service.get_variable_by_id(variable_id) - - - ################# - # Edits - ################# - - def change_value(self, value, operator): - filtered_points = self.get_filtered_points() - - ids = filtered_points["ValueID"].astype(int).tolist() - self.memDB.updateValue(ids, operator, float(value)) - self._populate_series() - - ## update filtered_dataframe - self.filtered_dataframe = self._series_points_df[self._series_points_df['ValueID'].isin(ids)] - - def add_points(self, points): - # todo: add the ability to send in multiple datetimes to a single 'point' - - self.memDB.addPoints(points) - - self._populate_series() - self.reset_filter() - - def delete_points(self): - filtered_points = self.get_filtered_points() - if not filtered_points.empty: - values = filtered_points['ValueID'].astype(float).tolist() - - self.memDB.delete(values) - self._populate_series() - self.filtered_dataframe = None - - def interpolate(self): - ''' - In [75]: ser = Series(np.sort(np.random.uniform(size=100))) - # interpolate at new_index - In [76]: new_index = ser.index | Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75]) - In [77]: interp_s = ser.reindex(new_index).interpolate(method='pchip') - ''' - - tmp_filter_list =self.get_filtered_points() - df = self._series_points_df - issel = df.index.isin(tmp_filter_list.index) - - mdf = df["DataValue"].mask(issel) - mdf.interpolate(method = "time", inplace=True) - tmp_filter_list["DataValue"]=mdf[issel] - ids = tmp_filter_list['ValueID'].tolist() - - #update_list = [(row["DataValue"], row["ValueID"]) for index, row in tmp_filter_list.iterrows()] - update_list = [{"value": row["DataValue"], "id": row["ValueID"]} for index, row in tmp_filter_list.iterrows()] - - self.memDB.update(update_list) - - - self._populate_series() - - self.filtered_dataframe = self._series_points_df[self._series_points_df['ValueID'].isin(ids)] - - def drift_correction(self, gap_width): - if self.isOneGroup(): - tmp_filter_list =self.get_filtered_points() - startdate =tmp_filter_list.index[0] - x_l = (tmp_filter_list.index[-1]-startdate).total_seconds() - - # y_n = y_0 + G(x_i / x_l) - f = lambda row : row["DataValue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) - tmp_filter_list["DataValue"]=tmp_filter_list.apply(f, axis = 1) - - update_list = [{"value": row["DataValue"], "id": row["ValueID"]} for index, row in tmp_filter_list.iterrows()] - - ids = tmp_filter_list['ValueID'].tolist() - self.memDB.update(update_list) - - - self._populate_series() - - self.filtered_dataframe = self._series_points_df[self._series_points_df['ValueID'].isin(ids)] - return True - return False - - def isOneGroup(self): - - issel = self._series_points_df.index.isin(self.get_filtered_points().index) - - found_group = False - count = 0 - - for x in issel: - if x: - if not found_group: - found_group=True - count =count+1 - else: - found_group = False - - if count >1: - return False - if count == 1: - return True - - - def flag(self, qualifier_id): - - filtered_points = self.get_filtered_points() - ''' - query = "UPDATE DataValues SET QualifierID = %s WHERE ValueID = ?" % (qualifier_id) - #self._cursor.executemany(query, [(str(x[0]),) for x in filtered_points]) - self._cursor.executemany(query, [(str(x),) for x in filtered_points["ValueID"].astype(int).tolist()]) - ''' - self.memDB.updateFlag(filtered_points["ValueID"].astype(int).tolist(), qualifier_id) - - ################### - # Save/Restore - ################### - - def restore(self): - self.memDB.rollback() - - self._populate_series() - self.reset_filter() - - def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False): - """ - - :param var: - :param method: - :param qcl: - :param is_new_series: - :return: - """ - - var_id = var.id if var is not None else None - method_id = method.id if method is not None else None - qcl_id = qcl.id if qcl is not None else None - #self.memDB.changeSeriesIDs(var_id, method_id, qcl_id) - dvs = self.memDB.getDataValuesDF() - if var_id is not None: - dvs["VariableID"] = var_id - if method_id is not None: - dvs["MethodID"] = method_id - if qcl_id is not None: - dvs["QualityControlLevelID"] = qcl_id - - - - #if is new series remove valueids - #if is_new_series: - dvs["ValueID"] = None - ''' - for dv in dvs: - dv.id = None - ''' - - series = self.memDB.series_service.get_series_by_id(self._series_id) - logger.debug("original editing series id: %s" % str(series.id)) - - if (var or method or qcl ): - tseries = self.memDB.series_service.get_series_by_id_quint(site_id=int(series.site_id), - var_id=var_id if var else int(series.variable_id), - method_id=method_id if method else int( - series.method_id), - source_id=series.source_id, - qcl_id=qcl_id if qcl else int( - series.quality_control_level_id)) - if tseries: - logger.debug("Save existing series ID: %s" % str(series.id)) - series = tseries - else: - print "Series doesn't exist (if you are not, you should be running SaveAs)" - - if is_new_series: - - - series = series_module.copy_series(series) - - - if var: - series.variable_id = var_id - series.variable_code = var.code - series.variable_name = var.name - series.speciation = var.speciation - series.variable_units_id = var.variable_unit_id - series.variable_units_name = var.variable_unit.name - series.sample_medium = var.sample_medium - series.value_type = var.value_type - series.time_support = var.time_support - series.time_units_id = var.time_unit_id - series.time_units_name = var.time_unit.name - series.data_type = var.data_type - series.general_category = var.general_category - - if method: - series.method_id = method_id - series.method_description = method.description - - if qcl: - series.quality_control_level_id = qcl_id - series.quality_control_level_code = qcl.code - ''' - dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) - dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) - ''' - - - - - - form = "%Y-%m-%d %H:%M:%S" - series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[0].local_date_time - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time - series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc - series.value_count = len(dvs) - - ## Override previous save - if not is_new_series: - # delete old dvs - #pass - self.memDB.series_service.delete_values_by_series(series) - - - #logger.debug("series.data_values: %s" % ([x for x in series.data_values])) - dvs.drop('ValueID', axis=1, inplace=True) - return series, dvs - - def save(self): - """ Save to an existing catalog - :param var: - :param method: - :param qcl: - :return: - """ - - series, dvs = self.updateSeries(is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save was unsuccessful") - return False - - def save_as(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) - - if self.memDB.series_service.save_new_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save As Function was Unsuccessful") - return False - - def save_existing(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save As Existing Function was Unsuccessful") - return False - - def create_qcl(self, code, definition, explanation): - return self.memDB.series_service.create_qcl(code, definition, explanation) - - def create_method(self, description, link): - return self.memDB.series_service.create_method(description, link) - - def create_qualifier(self, code, definition): - return self.memDB.series_service.create_qualifier(code, definition) - - def create_variable(self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, general_category, no_data_value): - - return self.memDB.series_service.create_variable(code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value) - - def reconcile_dates(self, parent_series_id): - # FUTURE FEATURE: pull in new field data from another series and add to this series - # (i.e one series contains new field data of an edited series at a higher qcl) - pass - - diff --git a/odmtools/odmservices/export_service.py b/odmtools/odmservices/export_service.py deleted file mode 100644 index 4b6d1eb..0000000 --- a/odmtools/odmservices/export_service.py +++ /dev/null @@ -1,401 +0,0 @@ -import csv -import xml.etree.cElementTree as ET -import datetime - - -class ExportService(): - ''' - Create with the Service Manager!!! - ''' - - def __init__(self, series_service): - self._series_service = series_service - self.dt_format_str = "%m/%d/%Y %I:%M:%S %p" - - def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, - src=False, qcl=False): - series = self._series_service.get_series_by_id(series_id) - if series is None: - return False - - writer = csv.writer(open(filename, 'wb')) - print "filename: ", filename - self.write_data_header(writer, utc, site, var, offset, qual, src, qcl) - for dv in series.data_values: - self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) - - def write_data_row(self, writer, series, dv, utc, site, var, offset, qual, src, qcl): - data = [] - data.append(series.id) - data.append(dv.id) - data.append(dv.data_value) - data.append(dv.value_accuracy) - data.append(dv.local_date_time) - if utc: - data.append(dv.utc_offset) - data.append(dv.date_time_utc) - data.append(series.site_code) - if site: - data.append(series.site_name) - data.append(series.site.type) - data.append(series.site.latitude) - data.append(series.site.longitude) - data.append(series.site.spatial_ref.srs_name) - data.append(series.variable_code) - if var: - data.append(series.variable_name) - data.append(series.speciation) - data.append(series.variable_units_name) - data.append(series.variable.variable_unit.abbreviation) - data.append(series.sample_medium) - data.append(dv.offset_value) - data.append(dv.offset_type_id) - if offset: - if dv.offset_type is not None: - data.append(dv.offset_type.description) - data.append(dv.offset_type.unit.name) - else: - data.append('') - data.append('') - data.append(dv.censor_code) - data.append(dv.qualifier_id) - if qual: - if dv.qualifier is not None: - data.append(dv.qualifier.code) - data.append(dv.qualifier.description) - else: - data.append('') - data.append('') - if src: - data.append(series.organization) - data.append(series.source_description) - data.append(series.citation) - if qcl: - data.append(series.quality_control_level_code) - data.append(series.quality_control_level.definition) - data.append(series.quality_control_level.explanation) - data.append(dv.sample_id) - - writer.writerow(data) - - def write_data_header(self, writer, utc, site, var, offset, qual, src, qcl): - # Build header list - header = [] - header.append("SeriesId") - header.append("ValueId") - header.append("DataValue") - header.append("ValueAccuracy") - header.append("LocalDateTime") - if utc: - header.append("UTCOffset") - header.append("DateTimeUTC") - header.append("SiteCode") - if site: - header.append("SiteName") - header.append("SiteType") - header.append("Latitude") - header.append("Longitude") - header.append("SRSName") - header.append("VariableCode") - if var: - header.append("VariableName") - header.append("Speciation") - header.append("VariableUnitsName") - header.append("VariableUnitsAbbreviation") - header.append("SampleMedium") - header.append("OffsetValue") - header.append("OffsetTypeID") - if offset: - header.append("OffsetDescription") - header.append("OffsetUnitsName") - header.append("CensorCode") - header.append("QualifierID") - if qual: - header.append("QualifierCode") - header.append("QualifierDescription") - if src: - header.append("Organization") - header.append("SourceDescription") - header.append("Citation") - if qcl: - header.append("QualityControlLevelCode") - header.append("Definition") - header.append("Explanation") - header.append("SampleID") - - writer.writerow(header) - - def export_series_metadata(self, series_ids, filename): - if series_ids is None: - return - - root = ET.Element("Metadata") - list_root = ET.SubElement(root, "DataSeriesList") - list_root.set("Total", str(series_ids)) - - try: - with open(filename): - file_exists = True - except IOError: - file_exists = False - - if file_exists: - # Read the file into the XML tree - pass - - if isinstance(series_ids, int): - series = self._series_service.get_series_by_id(series_ids) - self.append_series_node(series, list_root) - else: - for series_id in series_ids: - series = self._series_service.get_series_by_id(series_id) - self.append_series_node(series, list_root) - - tree = ET.ElementTree(root) - tree.write(filename) - - def append_series_node(self, series, parent): - series_node = ET.SubElement(parent, "DataSeries") - series_node.set("ID", str(series.id)) - self.append_general_info(series, series_node) - self.append_site_info(series, series_node) - self.append_var_info(series, series_node) - self.append_method_source_info(series, series_node) - self.append_misc_info(series, series_node) - - return series_node - - def append_general_info(self, series, parent): - meta = series.source.iso_metadata - general_node = ET.SubElement(parent, "GeneralInformation") - topic = ET.SubElement(general_node, "TopicCategory") - topic.text = meta.topic_category - title = ET.SubElement(general_node, "Title") - title.text = meta.title - abstract = ET.SubElement(general_node, "Abstract") - abstract.text = meta.abstract - prof_version = ET.SubElement(general_node, "ProfileVersion") - prof_version.text = meta.profile_version - metadata_link = ET.SubElement(general_node, "MetadataLink") - metadata_link.text = meta.metadata_link - date = ET.SubElement(general_node, "MetadataCreationDate") - # 7/1/2013 12:17:16 PM - date.text = datetime.datetime.now().strftime(self.dt_format_str) - - def append_site_info(self, series, parent): - site = series.site - site_node = ET.SubElement(parent, "SiteInformation") - site_code = ET.SubElement(site_node, "SiteCode") - site_code.text = site.code - site_name = ET.SubElement(site_node, "SiteName") - site_name.text = site.name - site_type = ET.SubElement(site_node, "SiteType") - site_type.text = site.type - - geo_coords = ET.SubElement(site_node, "GeographicCoordinates") - latitude = ET.SubElement(geo_coords, "Latitude") - latitude.text = str(site.latitude) - longitude = ET.SubElement(geo_coords, "Longitude") - longitude.text = str(site.longitude) - srs_id = ET.SubElement(geo_coords, "SRSID") - srs_id.text = str(site.spatial_ref.srs_id) - srs_name = ET.SubElement(geo_coords, "SRSName") - srs_name.text = site.spatial_ref.srs_name - is_geo = ET.SubElement(geo_coords, "IsGeographic") - is_geo.text = str(site.spatial_ref.is_geographic) - notes = ET.SubElement(geo_coords, "Notes") - notes.text = site.spatial_ref.notes - - local_coords = ET.SubElement(site_node, "LocalCoordinates") - local_x = ET.SubElement(local_coords, "LocalX") - local_x.text = str(site.local_x) - local_y = ET.SubElement(local_coords, "LocalY") - local_y.text = str(site.local_y) - local_srs_id = ET.SubElement(local_coords, "SRSID") - local_srs_id.text = str(site.local_spatial_ref.srs_id) - local_srs_name = ET.SubElement(local_coords, "SRSName") - local_srs_name.text = site.local_spatial_ref.srs_name - local_is_geo = ET.SubElement(local_coords, "IsGeographic") - local_is_geo.text = str(site.local_spatial_ref.is_geographic) - local_notes = ET.SubElement(local_coords, "Notes") - local_notes.text = site.local_spatial_ref.notes - elevation = ET.SubElement(local_coords, "Elevation_m") - if site.elevation_m: elevation.text = str(site.elevation_m) - vert_datum = ET.SubElement(local_coords, "VerticalDatum") - if site.vertical_datum_id: vert_datum.text = str(site.vertical_datum_id) - - pos_accuracy = ET.SubElement(site_node, "PosAccuracy_m") - pos_accuracy.text = str(site.pos_accuracy_m) - state = ET.SubElement(site_node, "State") - state.text = site.state - county = ET.SubElement(site_node, "County") - county.text = site.county - comments = ET.SubElement(site_node, "Comments") - comments.text = site.comments - - def append_var_info(self, series, parent): - variable = series.variable - var_node = ET.SubElement(parent, "VariableInformation") - - var_code = ET.SubElement(var_node, "VariableCode") - var_code.text = variable.code - var_name = ET.SubElement(var_node, "VariableName") - var_name.text = variable.name - speciation = ET.SubElement(var_node, "Speciation") - speciation.text = variable.speciation - - var_units = ET.SubElement(var_node, "VariableUnits") - units_name = ET.SubElement(var_units, "UnitsName") - units_name.text = variable.variable_unit.name - units_type = ET.SubElement(var_units, "UnitsType") - units_type.text = variable.variable_unit.type - units_abbrev = ET.SubElement(var_units, "UnitsAbbreviation") - units_abbrev.text = variable.variable_unit.abbreviation - - sample_medium = ET.SubElement(var_node, "SampleMedium") - sample_medium.text = variable.sample_medium - val_type = ET.SubElement(var_node, "ValueType") - val_type.text = variable.value_type - is_reg = ET.SubElement(var_node, "IsRegular") - is_reg.text = str(variable.is_regular) - time_support = ET.SubElement(var_node, "TimeSupport") - time_support.text = str(variable.time_support) - - time_support_units = ET.SubElement(var_node, "TimeSupportUnits") - ts_units_name = ET.SubElement(time_support_units, "UnitsName") - ts_units_name.text = variable.time_unit.name - ts_units_type = ET.SubElement(time_support_units, "UnitsType") - ts_units_type.text = variable.time_unit.type - ts_units_abbrev = ET.SubElement(time_support_units, "UnitsAbbreviation") - ts_units_abbrev.text = variable.time_unit.abbreviation - - data_type = ET.SubElement(var_node, "DataType") - data_type.text = variable.data_type - gen_cat = ET.SubElement(var_node, "GeneralCategory") - gen_cat.text = variable.general_category - no_dv = ET.SubElement(var_node, "NoDataValue") - no_dv.text = str(variable.no_data_value) - - period = ET.SubElement(var_node, "PeriodOfRecord") - begin_dt = ET.SubElement(period, "BeginDateTime") - begin_dt.text = series.begin_date_time.strftime(self.dt_format_str) - end_dt = ET.SubElement(period, "EndDateTime") - end_dt.text = series.end_date_time.strftime(self.dt_format_str) - begin_dt_utc = ET.SubElement(period, "BeginDateTimeUTC") - begin_dt_utc.text = series.begin_date_time_utc.strftime(self.dt_format_str) - end_dt_utc = ET.SubElement(period, "EndDateTimeUTC") - end_dt_utc.text = series.end_date_time_utc.strftime(self.dt_format_str) - value_count = ET.SubElement(period, "ValueCount") - value_count.text = str(series.value_count) - - def append_method_source_info(self, series, parent): - method = series.method - method_node = ET.SubElement(parent, "MethodInformation") - method_desc = ET.SubElement(method_node, "MethodDescription") - method_desc.text = method.description - method_link = ET.SubElement(method_node, "MethodLink") - method_link.text = method.link - - source = series.source - source_node = ET.SubElement(parent, "SourceInformation") - org = ET.SubElement(source_node, "Organization") - org.text = source.organization - source_desc = ET.SubElement(source_node, "SourceDescription") - source_desc.text = source.description - source_link = ET.SubElement(source_node, "SourceLink") - source_link.text = source.link - - contact = ET.SubElement(source_node, "Contact") - contact_name = ET.SubElement(contact, "ContactName") - contact_name.text = source.contact_name - phone = ET.SubElement(contact, "Phone") - phone.text = source.phone - email = ET.SubElement(contact, "Email") - email.text = source.email - address = ET.SubElement(contact, "Address") - address.text = source.address - city = ET.SubElement(contact, "City") - city.text = source.city - state = ET.SubElement(contact, "State") - state.text = source.state - zip_code = ET.SubElement(contact, "ZipCode") - zip_code.text = source.zip_code - - citation = ET.SubElement(source_node, "Citation") - citation.text = source.citation - - def append_misc_info(self, series, parent): - qcl = series.quality_control_level - - qcl_node = ET.SubElement(parent, "QualityControlLevelInformation") - qcl_code = ET.SubElement(qcl_node, "QualityControlLevelCode") - qcl_code.text = qcl.code - qcl_def = ET.SubElement(qcl_node, "Definition") - qcl_def.text = qcl.definition - qcl_expl = ET.SubElement(qcl_node, "Explanation") - qcl_expl.text = qcl.explanation - - offsets_node = ET.SubElement(parent, "OffsetInformation") - offsets = self._series_service.get_offset_types_by_series_id(series.id) - for offset in offsets: - offset_id = ET.SubElement(offsets_node, "Offset") - if offset: - offset_id.set("ID", str(offset.id)) - else: - offset_id.set("ID", "") - offset_desc = ET.SubElement(offsets_node, "OffsetDescription") - if offset: offset_desc.text = offset.description - offset_units = ET.SubElement(offsets_node, "OffsetUnits") - units_name = ET.SubElement(offset_units, "UnitsName") - if offset: units_name.text = offset.unit.name - units_type = ET.SubElement(offset_units, "UnitsType") - if offset: units_type.text = offset.unit.type - units_abbrev = ET.SubElement(offset_units, "UnitsAbbreviation") - if offset: units_abbrev.text = offset.unit.abbreviation - - qualifiers_node = ET.SubElement(parent, "QualifierInformation") - qualifiers = self._series_service.get_qualifiers_by_series_id(series.id) - for qual in qualifiers: - qual_id = ET.SubElement(qualifiers_node, "Qualifier") - if qual: - qual_id.set("ID", str(qual.id)) - else: - qual_id.set("ID", "") - qual_code = ET.SubElement(qual_id, "QualiferCode") - if qual: qual_code.text = qual.code - qual_desc = ET.SubElement(qual_id, "QualifierDescription") - if qual: qual_desc.text = qual.description - - samples_node = ET.SubElement(parent, "SampleInformation") - samples = self._series_service.get_samples_by_series_id(series.id) - for sample in samples: - sample_id = ET.SubElement(samples_node, "Sample") - if sample: - sample_id.set("ID", str(sample.id)) - else: - sample_id.set("ID", "") - sample_type = ET.SubElement(sample_id, "SampleType") - if sample: sample_type.text = sample.type - lab_code = ET.SubElement(sample_id, "LabSampleCode") - if sample: lab_code.text = sample.lab_sample_code - lab_method_id = ET.SubElement(sample_id, "LabMethodID") - if sample: lab_method_id = sample.lab_method_id - - lab_method_node = ET.SubElement(parent, "LabMethodInformation") - for sample in samples: - if sample: lab_method = sample.lab_method - lab_method_id = ET.SubElement(lab_method_node, "LabMethod") - if lab_method: - lab_method_id.set("ID", str(lab_method.id)) - else: - lab_method_id.set("ID", "") - lab_name = ET.SubElement(lab_method_id, "LabName") - if lab_method: lab_name.text = lab_method.name - lab_org = ET.SubElement(lab_method_id, "LabOrganization") - if lab_method: lab_org.text = lab_method.organization - method_name = ET.SubElement(lab_method_id, "LabMethodName") - if lab_method: method_name.text = lab_method.method_name - method_desc = ET.SubElement(lab_method_id, "LabMethodDescription") - if lab_method: method_desc.text = lab_method.method_description - method_link = ET.SubElement(lab_method_id, "LabMethodLink") - if lab_method: method_link.text = lab_method.link \ No newline at end of file diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py deleted file mode 100644 index 6827984..0000000 --- a/odmtools/odmservices/series_service.py +++ /dev/null @@ -1,782 +0,0 @@ -import logging - - -from sqlalchemy import distinct, func - - -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import Site -from odmtools.odmdata import Variable -from odmtools.odmdata import Unit -from odmtools.odmdata import Series -from odmtools.odmdata import DataValue -from odmtools.odmdata import Qualifier -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Method -from odmtools.odmdata import QualityControlLevel -from odmtools.odmdata import ODMVersion -from odmtools.common.logger import LoggerTool -import pandas as pd - -tool = LoggerTool() -logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) - - -class SeriesService(): - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug - - def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks - - def get_db_version(self): - return self._edit_session.query(ODMVersion).first().version_number - -##################### -# -# Get functions -# -##################### - - # Site methods - def get_all_sites(self): - """ - - :return: List[Sites] - """ - return self._edit_session.query(Site).order_by(Site.code).all() - - - def get_used_sites(self): - """ - Return a list of all sites that are being referenced in the Series Catalog Table - :return: List[Sites] - """ - try: - site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] - except: - site_ids = None - - if not site_ids: - return None - - Sites = [] - for site_id in site_ids: - Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) - - return Sites - - - def get_site_by_id(self, site_id): - """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites - """ - try: - return self._edit_session.query(Site).filter_by(id=site_id).first() - except: - return None - - # Variables methods - def get_used_variables(self): - """ - #get list of used variable ids - :return: List[Variables] - """ - - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] - except: - var_ids = None - - if not var_ids: - return None - - Variables = [] - - #create list of variables from the list of ids - for var_id in var_ids: - Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return Variables - - def get_all_variables(self): - """ - - :return: List[Variables] - """ - return self._edit_session.query(Variable).all() - - def get_variable_by_id(self, variable_id): - """ - - :param variable_id: int - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() - except: - return None - - def get_variable_by_code(self, variable_code): - """ - - :param variable_code: str - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() - except: - return None - - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] - """ - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( - site_code=site_code).all()] - except: - var_ids = None - - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return variables - - # Unit methods - def get_all_units(self): - """ - - :return: List[Units] - """ - return self._edit_session.query(Unit).all() - - def get_unit_by_name(self, unit_name): - """ - - :param unit_name: str - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None - - def get_unit_by_id(self, unit_id): - """ - - :param unit_id: int - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() - except: - return None - - - def get_all_qualifiers(self): - """ - - :return: List[Qualifiers] - """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result - - def get_qualifiers_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() - - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() - - def get_qcl_by_id(self, qcl_id): - try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() - except: - return None - - def get_qcl_by_code(self, qcl_code): - try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() - except: - return None - - # Method methods - def get_all_methods(self): - return self._edit_session.query(Method).all() - - def get_method_by_id(self, method_id): - try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() - except: - result = None - return result - - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() - except: - result = None - return result - - def get_offset_types_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() - return self._edit_session.query(OffsetType).join(subquery).distinct().all() - - def get_samples_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.sample_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() - return self._edit_session.query(Sample).join(subquery).distinct().all() - - # Series Catalog methods - def get_all_series(self): - """ - Returns all series as a modelObject - :return: List[Series] - """ - - #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) - return self._edit_session.query(Series).order_by(Series.id).all() - - def get_series_by_site(self , site_id): - """ - - :param site_id: int - :return: List[Series] - """ - try: - selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() - return selectedSeries - except: - return None - - def get_series_by_id(self, series_id): - """ - - :param series_id: int - :return: Series - """ - try: - return self._edit_session.query(Series).filter_by(id=series_id).first() - except Exception as e: - print e - return None - - def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: Series - """ - try: - return self._edit_session.query(Series).filter_by( - site_id=site_id, variable_id=var_id, method_id=method_id, - source_id=source_id, quality_control_level_id=qcl_id).first() - except: - return None - - def get_series_from_filter(self): - # Pass in probably a Series object, match it against the database - pass - - - #Data Value Methods - def get_values_by_series(self, series_id): - ''' - - :param series_id: Series id - :return: pandas dataframe - ''' - series= self.get_series_by_id(series_id) - if series: - q = self._edit_session.query(DataValue).filter_by( - site_id=series.site_id, - variable_id=series.variable_id, - method_id=series.method_id, - source_id=series.source_id, - quality_control_level_id=series.quality_control_level_id) - - query=q.statement.compile(dialect=self._session_factory.engine.dialect) - data= pd.read_sql_query(sql= query, - con = self._session_factory.engine, - params = query.params ) - #return data.set_index(data['LocalDateTime']) - return data - else: - return None - - def get_all_values_df(self): - """ - - :return: Pandas DataFrame object - """ - q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) - columns = list(data) - - columns.insert(0, columns.pop(columns.index("DataValue"))) - columns.insert(1, columns.pop(columns.index("LocalDateTime"))) - columns.insert(2, columns.pop(columns.index("QualifierID"))) - - data = data.ix[:, columns] - return data.set_index(data['LocalDateTime']) - - def get_all_values_list(self): - """ - - :return: - """ - result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - return [x.list_repr() for x in result] - - def get_all_values(self): - return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - - @staticmethod - def calcSeason(row): - - month = int(row["Month"]) - - if month in [1, 2, 3]: - return 1 - elif month in[4, 5, 6]: - return 2 - elif month in [7, 8, 9]: - return 3 - elif month in [10, 11, 12]: - return 4 - - def get_all_plot_values(self): - """ - - :return: - """ - q = self._edit_session.query(DataValue.data_value.label('DataValue'), - DataValue.local_date_time.label('LocalDateTime'), - DataValue.censor_code.label('CensorCode'), - func.strftime('%m', DataValue.local_date_time).label('Month'), - func.strftime('%Y', DataValue.local_date_time).label('Year') - #DataValue.local_date_time.strftime('%m'), - #DataValue.local_date_time.strftime('%Y')) - ).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data.set_index(data['LocalDateTime']) - - def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): - """ - - :param seriesID: - :param noDataValue: - :param startDate: - :param endDate: - :return: - """ - series = self.get_series_by_id(seriesID) - - DataValues = [ - (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), - dv.local_date_time.strftime('%Y')) - for dv in series.data_values - if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate - ] - data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) - data.set_index(data['LocalDateTime'], inplace=True) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data - - - - def get_data_value_by_id(self, id): - """ - - :param id: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(id=id).first() - except: - return None - - - - -##################### -# -#Update functions -# -##################### - def update_series(self, series): - """ - - :param series: - :return: - """ - merged_series = self._edit_session.merge(series) - self._edit_session.add(merged_series) - self._edit_session.commit() - - def update_dvs(self, dv_list): - """ - - :param dv_list: - :return: - """ - merged_dv_list = map(self._edit_session.merge, dv_list) - self._edit_session.add_all(merged_dv_list) - self._edit_session.commit() - -##################### -# -#Create functions -# -##################### - def save_series(self, series, dvs): - """ Save to an Existing Series - :param series: - :param data_values: - :return: - """ - - if self.series_exists(series): - - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - logger.debug("Existing File was overwritten with new information") - return True - else: - logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") - # there wasn't an existing file to overwrite - raise Exception("Series does not exist, unable to save. Please select 'Save As'") - - - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.debug(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - - logger.debug("A new series was added to the database, series id: "+str(series.id)) - return True - - def save_values(self, values): - """ - - :param values: pandas dataframe - :return: - """ - values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) - - - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - """ - - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Series() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - - self._edit_session.add(series) - self._edit_session.commit() - return series - - def create_method(self, description, link): - """ - - :param description: - :param link: - :return: - """ - meth = Method() - meth.description = description - if link is not None: - meth.link = link - - self._edit_session.add(meth) - self._edit_session.commit() - return meth - - def create_variable_by_var(self, var): - """ - - :param var: Variable Object - :return: - """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None - - def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): - """ - - :param code: - :param name: - :param speciation: - :param variable_unit_id: - :param sample_medium: - :param value_type: - :param is_regular: - :param time_support: - :param time_unit_id: - :param data_type: - :param general_category: - :param no_data_value: - :return: - """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - - self._edit_session.add(var) - self._edit_session.commit() - return var - - def create_qcl(self, code, definition, explanation): - """ - - :param code: - :param definition: - :param explanation: - :return: - """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - - self._edit_session.add(qcl) - self._edit_session.commit() - return qcl - - - def create_qualifier_by_qual(self, qualifier): - self._edit_session.add(qualifier) - self._edit_session.commit() - return qualifier - - def create_qualifier(self, code, description): - """ - - :param code: - :param description: - :return: - """ - qual = Qualifier() - qual.code = code - qual.description = description - - return self.create_qualifier_by_qual(qual) - -##################### -# -# Delete functions -# -##################### - - def delete_series(self, series): - """ - - :param series: - :return: - """ - self.delete_values_by_series(series) - - delete_series = self._edit_session.merge(series) - self._edit_session.delete(delete_series) - self._edit_session.commit() - - - def delete_values_by_series(self, series): - """ - - :param series: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - variable_id = series.variable_id, - method_id = series.method_id, - source_id = series.source_id, - quality_control_level_id = series.quality_control_level_id).delete() - except: - return None - - def delete_dvs(self, id_list): - """ - - :param id_list: list of ids - :return: - """ - self._edit_session.query(DataValue).filter(DataValue.id.in_(id_list)).delete(False) - -##################### -# -#Exist functions -# -##################### - - - def series_exists(self, series): - """ - - :param series: - :return: - """ - return self.series_exists_quint( - series.site_id, - series.variable_id, - series.method_id, - series.source_id, - series.quality_control_level_id - ) - - def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - try: - result = self._edit_session.query(Series).filter_by( - site_id=site_id, - variable_id=var_id, - method_id=method_id, - source_id=source_id, - quality_control_level_id=qcl_id - ).one() - - return True - except: - return False - def qcl_exists(self, q): - """ - - :param q: - :return: - """ - try: - result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() - return True - except: - - return False - - def method_exists(self, m): - """ - - :param m: - :return: - """ - try: - result = self._edit_session.query(Method).filter_by(description=m.description).one() - return True - except: - return False - - def variable_exists(self, v): - """ - - :param v: - :return: - """ - try: - result = self._edit_session.query(Variable).filter_by(code=v.code, - name=v.name, speciation=v.speciation, - variable_unit_id=v.variable_unit_id, - sample_medium=v.sample_medium, - value_type=v.value_type, is_regular=v.is_regular, - time_support=v.time_support, - time_unit_id=v.time_unit_id, data_type=v.data_type, - general_category=v.general_category, - no_data_value=v.no_data_value).one() - return result - except: - return None \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py deleted file mode 100755 index f7c3f6e..0000000 --- a/odmtools/odmservices/service_manager.py +++ /dev/null @@ -1,216 +0,0 @@ -import logging -import os -import sys - -import urllib - -from sqlalchemy.exc import SQLAlchemyError#OperationalError, DBAPIError - -from odmtools.common.logger import LoggerTool -from series_service import SeriesService -from cv_service import CVService -from edit_service import EditService -from odmtools.controller import EditTools -from export_service import ExportService -from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata.session_factory import SessionFactory - - -tool = LoggerTool() -logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) - - -class ServiceManager(): - def __init__(self, debug=False): - self.debug = debug - f = self._get_file('r') - self._conn_dicts = [] - self.version = 0 - self._connection_format = "%s+%s://%s:%s@%s/%s" - - # Read all lines (connections) in the connection.cfg file - while True: - line = f.readline() - if not line: - break - else: - line = line.split() - #logger.debug(line) - - if len(line) >= 5: - line_dict = {} - - line_dict['engine'] = line[0] - line_dict['user'] = line[1] - line_dict['password'] = line[2] - line_dict['address'] = line[3] - line_dict['db'] = line[4] - self._conn_dicts.append(line_dict) - - if len(self._conn_dicts) is not 0: - # The current connection defaults to the most recent (i.e. the last written to the file) - self._current_conn_dict = self._conn_dicts[-1] - else: - self._current_conn_dict = None - - f.close() - - def get_all_conn_dicts(self): - return self._conn_dicts - - def is_valid_connection(self): - if self._current_conn_dict: - conn_string = self._build_connection_string(self._current_conn_dict) - logger.debug("Conn_string: %s" % conn_string) - try: - if self.testEngine(conn_string): - return self.get_current_conn_dict() - except Exception as e: - logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) - return None - return None - - def get_current_conn_dict(self): - return self._current_conn_dict - - def set_current_conn_dict(self, dict): - self._current_conn_dict = dict - - def add_connection(self, conn_dict): - """conn_dict must be a dictionary with keys: engine, user, password, address, db""" - - # remove earlier connections that are identical to this one - self.delete_connection(conn_dict) - - if self.test_connection(conn_dict): - # write changes to connection file - self._conn_dicts.append(conn_dict) - self._current_conn_dict = self._conn_dicts[-1] - self._save_connections() - return True - else: - logger.error("Unable to save connection due to invalid connection to database") - return False - - - @classmethod - def testEngine(self, connection_string): - s = SessionFactory(connection_string, echo=False) - if 'mssql' in connection_string: - s.ms_test_Session().execute("Select top 1 VariableCode From Variables") - elif 'mysql' in connection_string: - s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') - elif 'postgresql' in connection_string: - #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') - return True - - def test_connection(self, conn_dict): - try: - conn_string = self._build_connection_string(conn_dict) - if self.testEngine(conn_string) and self.get_db_version(conn_string) == '1.1.1': - return True - except SQLAlchemyError as e: - logger.error("SQLAlchemy Error: %s" % e.message) - raise e - except Exception as e: - logger.error("Error: %s" % e) - raise e - return False - - def delete_connection(self, conn_dict): - self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] - - # Create and return services based on the currently active connection - def get_db_version_dict(self, conn_dict): - conn_string = self._build_connection_string(conn_dict) - self.get_db_version(conn_string) - - def get_db_version(self, conn_string): - if isinstance(conn_string, dict): - conn_string = self._build_connection_string(conn_string) - service = SeriesService(conn_string) - #if not self.version: - try: - self.version = service.get_db_version() - except Exception as e: - logger.error("Exception: %s" % e.message) - return None - return self.version - - def get_series_service(self, conn_dict=""): - conn_string = "" - if conn_dict: - conn_string = self._build_connection_string(conn_dict) - self._current_conn_dict = conn_dict - else: - conn_string = self._build_connection_string(self._current_conn_dict) - return SeriesService(conn_string, self.debug) - - def get_cv_service(self): - conn_string = self._build_connection_string(self._current_conn_dict) - return CVService(conn_string, self.debug) - - def get_edit_service(self, series_id, connection): - - return EditService(series_id, connection=connection, debug=self.debug) - - def get_record_service(self, script, series_id, connection): - return EditTools(self, script, self.get_edit_service(series_id, connection), - self._build_connection_string(self.is_valid_connection())) - - def get_export_service(self): - return ExportService(self.get_series_service()) - - ## ################### - # private variables - ## ################### - - def _get_file(self, mode): - #fn = util.resource_path('connection.config') - fn = os.path.join(user_config_dir("ODMTools", "UCHIC"), 'connection.config') - - config_file = None - try: - - if os.path.exists(fn): - config_file = open(fn, mode) - else: - os.makedirs(user_config_dir("ODMTools", "UCHIC")) - open(fn, 'w').close() - config_file = open(fn, mode) - except: - open(fn, 'w').close() - config_file = open(fn, mode) - - - return config_file - - def _build_connection_string(self, conn_dict): - driver = "" - if conn_dict['engine'] == 'mssql' and sys.platform != 'win32': - driver = "pyodbc" - quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;' % (conn_dict['address'], conn_dict['user'], conn_dict['password'])) - conn_string = 'mssql+pyodbc:///?odbc_connect={}'.format(quoted) - - else: - if conn_dict['engine'] == 'mssql': - driver = "pyodbc" - elif conn_dict['engine'] == 'mysql': - driver = "pymysql" - elif conn_dict['engine'] == 'postgresql': - driver = "psycopg2" - else: - driver = "None" - - conn_string = self._connection_format % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], - conn_dict['db']) - return conn_string - - def _save_connections(self): - f = self._get_file('w') - for conn in self._conn_dicts: - f.write("%s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'])) - f.close() - From c82f8f220f7bfbb2649f1f2b4a190ba781d85b26 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Wed, 15 Jul 2015 16:50:01 -0600 Subject: [PATCH 002/158] change imports form sessionfactory --- odmtools/__init__.py | 2 -- odmtools/odmdata/__init__.py | 9 ++++++++- setup/__init__.py | 1 + make.py => setup/make.py | 0 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 setup/__init__.py rename make.py => setup/make.py (100%) diff --git a/odmtools/__init__.py b/odmtools/__init__.py index df60136..5381d99 100644 --- a/odmtools/__init__.py +++ b/odmtools/__init__.py @@ -1,6 +1,5 @@ __author__ = 'Jacob' import odmdata -import odmservices import controller import gui import lib @@ -9,7 +8,6 @@ __all__ = [ 'odmdata', - 'odmservices', 'controller', 'gui', 'lib', diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 9e7c314..8c29f9a 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -69,4 +69,11 @@ import sys sys.path.append('/Users/stephanie/DEV/ODM2PythonAPI/') import src.api as api -from src.api.ODMconnection import SessionFactory \ No newline at end of file +from src.api.ODMconnection import SessionFactory + + + +__all__=['MemoryDatabase', + 'SessionFactory', + 'api', + ] \ No newline at end of file diff --git a/setup/__init__.py b/setup/__init__.py new file mode 100644 index 0000000..a24dbc6 --- /dev/null +++ b/setup/__init__.py @@ -0,0 +1 @@ +__author__ = 'stephanie' diff --git a/make.py b/setup/make.py similarity index 100% rename from make.py rename to setup/make.py From 3b4a413fb6eb66beed9342b6e67d8b24d3fb2a71 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Fri, 14 Aug 2015 15:02:19 -0600 Subject: [PATCH 003/158] get code working with api remove extra files --- odmtools/controller/olvSeriesSelector.py | 2 +- odmtools/odmdata/__init__.py | 48 +- odmtools/odmdata/memory_database.py | 7 +- odmtools/odmdata/series.py | 129 ---- odmtools/odmservices/__init__.py | 22 +- odmtools/odmservices/edit_service.py | 583 ----------------- odmtools/odmservices/series_service.py | 782 ----------------------- odmtools/odmservices/service_manager.py | 208 ++++++ 8 files changed, 264 insertions(+), 1517 deletions(-) delete mode 100644 odmtools/odmdata/series.py delete mode 100644 odmtools/odmservices/edit_service.py delete mode 100644 odmtools/odmservices/series_service.py create mode 100755 odmtools/odmservices/service_manager.py diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index d8f0ab7..7f21f32 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -6,7 +6,7 @@ from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn from odmtools.common.logger import LoggerTool -from odmtools.odmdata import series +from odmtools.odmdata import Series tool = LoggerTool() diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index aa68242..447f960 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -66,18 +66,48 @@ ] ''' -<<<<<<< HEAD + import sys -sys.path.append('/Users/stephanie/DEV/ODM2PythonAPI/') -import src.api as api +from odmtools.odmdata import memory_database + + from src.api.ODMconnection import SessionFactory +from src.api.versionSwitcher.ODM import DataTypeCV, DataValue, GeneralCategoryCV, ISOMetadata, LabMethod, Method, \ + OffsetType,Qualifier,QualityControlLevel,Sample,SampleMediumCV,SampleTypeCV, Series, Site,SiteTypeCV,Source, \ + SpatialReference,SpeciationCV,TopicCategoryCV,Unit,ValueTypeCV,Variable,VerticalDatumCV -__all__=['MemoryDatabase', - 'SessionFactory', - 'api', - ] -======= +__all__=[ + 'SessionFactory', + 'CensorCodeCV', + 'DataTypeCV', + 'DataValue', + 'GeneralCategoryCV', + 'ISOMetadata', + 'LabMethod', + 'Method', + 'ODMVersion', + 'OffsetType', + 'Qualifier', + 'QualityControlLevel', + 'Sample', + 'SampleMediumCV', + 'SampleTypeCV', + 'Series', + 'SessionFactory', + 'Site', + 'SiteTypeCV', + 'Source', + 'SpatialReference', + 'SpeciationCV', + 'TopicCategoryCV', + 'Unit', + 'ValueTypeCV', + 'Variable', + 'VariableNameCV', + 'VerticalDatumCV', + + 'MemoryDatabase', ->>>>>>> 60dccccf7d52506b8bdb8cfd063f73ce2dad2b4b + ] diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 9c3d241..00b1156 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -1,11 +1,10 @@ -import timeit import logging + +from sqlalchemy import bindparam + from odmtools.common.logger import LoggerTool from odmtools.odmservices import SeriesService from odmtools.odmdata import DataValue -from sqlalchemy import update, bindparam -from odmtools.common.taskServer import TaskServerMP -from multiprocessing import cpu_count, freeze_support tool = LoggerTool() logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py deleted file mode 100644 index 3510dbf..0000000 --- a/odmtools/odmdata/series.py +++ /dev/null @@ -1,129 +0,0 @@ -from collections import OrderedDict # Requires Python 2.7 >= - -from sqlalchemy import Column, Integer, ForeignKey, String, Float, DateTime -from sqlalchemy.orm import relationship - -from base import Base -from site import Site -from variable import Variable -from method import Method -from source import Source -from quality_control_level import QualityControlLevel - -def copy_series(from_series): - new = Series() - new.site_id = from_series.site_id - new.site_code = from_series.site_code - new.site_name = from_series.site_name - new.variable_id = from_series.variable_id - new.variable_code = from_series.variable_code - new.variable_name = from_series.variable_name - new.speciation = from_series.speciation - new.variable_units_id = from_series.variable_units_id - new.variable_units_name = from_series.variable_units_name - new.sample_medium = from_series.sample_medium - new.value_type = from_series.value_type - new.time_support = from_series.time_support - new.time_units_id = from_series.time_units_id - new.time_units_name = from_series.time_units_name - new.data_type = from_series.data_type - new.general_category = from_series.general_category - new.method_id = from_series.method_id - new.method_description = from_series.method_description - new.source_id = from_series.source_id - new.source_description = from_series.source_description - new.organization = from_series.organization - new.citation = from_series.citation - new.quality_control_level_id = from_series.quality_control_level_id - new.quality_control_level_code = from_series.quality_control_level_code - new.begin_date_time = from_series.begin_date_time - new.begin_date_time_utc = from_series.begin_date_time_utc - new.end_date_time_utc = from_series.end_date_time_utc - new.value_count = from_series.value_count - return new -class Series(Base): - __tablename__ = 'seriescatalog' - - id = Column('SeriesID', Integer, primary_key=True) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - site_code = Column('SiteCode', String) - site_name = Column('SiteName', String) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - variable_code = Column('VariableCode', String) - variable_name = Column('VariableName', String) - speciation = Column('Speciation', String) - variable_units_id = Column('VariableUnitsID', Integer) - variable_units_name = Column('VariableUnitsName', String) - sample_medium = Column('SampleMedium', String) - value_type = Column('ValueType', String) - time_support = Column('TimeSupport', Float) - time_units_id = Column('TimeUnitsID', Integer) - time_units_name = Column('TimeUnitsName', String) - data_type = Column('DataType', String) - general_category = Column('GeneralCategory', String) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - method_description = Column('MethodDescription', String) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - source_description = Column('SourceDescription', String) - organization = Column('Organization', String) - citation = Column('Citation', String) - quality_control_level_id = Column('QualityControlLevelID', Integer, - ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - quality_control_level_code = Column('QualityControlLevelCode', String) - begin_date_time = Column('BeginDateTime', DateTime) - end_date_time = Column('EndDateTime', DateTime) - begin_date_time_utc = Column('BeginDateTimeUTC', DateTime) - end_date_time_utc = Column('EndDateTimeUTC', DateTime) - value_count = Column('ValueCount', Integer) - - data_values = relationship("DataValue", - primaryjoin="and_(DataValue.site_id == Series.site_id, " - "DataValue.variable_id == Series.variable_id, " - "DataValue.method_id == Series.method_id, " - "DataValue.source_id == Series.source_id, " - "DataValue.quality_control_level_id == Series.quality_control_level_id)", - foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", - order_by="DataValue.local_date_time", - backref="series") - - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - - def __repr__(self): - return "" % (self.id, self.site_name, self.variable_code, self.variable_name) - - - def get_table_columns(self): - return self.__table__.columns.keys() - - def list_repr(self): - return [self.id, self.site_id, self.site_code, self.site_name, self.variable_id, self.variable_code, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.quality_control_level_code, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] - -def returnDict(): - keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) - - - diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 6d161f5..c850d5c 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -1,13 +1,17 @@ +# from service_manager import ServiceManager +# from series_service import SeriesService +# from cv_service import CVService +# from edit_service import EditService +# from export_service import ExportService +# +# # need to explicitly import these for pyinstaller +# import pymysql +# import pyodbc +# #import psycopg2 +import sys +sys.path.append('/Users/stephanie/DEV/ODM2PythonAPI/') +from src.api.ODM1_1_1.services import SeriesService, EditService, CVService, ExportService from service_manager import ServiceManager -from series_service import SeriesService -from cv_service import CVService -from edit_service import EditService -from export_service import ExportService - -# need to explicitly import these for pyinstaller -import pymysql -import pyodbc -#import psycopg2 __all__ = [ 'EditService', diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py deleted file mode 100644 index 4c625ab..0000000 --- a/odmtools/odmservices/edit_service.py +++ /dev/null @@ -1,583 +0,0 @@ -import sqlite3 - - -from odmtools.odmdata import DataValue -from series_service import SeriesService - -from odmtools.odmdata import series as series_module - -import pandas as pd -import datetime -import numpy as np - -import logging -from odmtools.common.logger import LoggerTool - -tool = LoggerTool() -logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) - - -class EditService(): - # Mutual exclusion: cursor, or connection_string - def __init__(self, series_id, connection=None, connection_string="", debug=False): - ''' - - :param series_id: - :param connection: memory database, contains connection to remote database - :param connection_string: connection to remote database - :param debug: - :return: - ''' - - self._series_id = series_id - self._filter_from_selection = False - self._debug = debug - - if connection_string is "" and connection is not None: - self.memDB= connection - #self._series_service = self.memDB.series_service#SeriesService(connection_string, debug) - - elif connection_string is not "" and connection is None: - from odmtools.odmdata import MemoryDatabase - self.memDB= MemoryDatabase()#(series_service) - self.memDB.set_series_service(SeriesService(connection_string, False)) - - - else: - logger.error("must send in either a remote db connection string or a memory database object") - - logger.debug("Initializing Memory Database") - self.memDB.initEditValues(series_id) - logger.debug("Finished Initializing Memory Database") - self._populate_series() - self.reset_filter() - - def get_series_service(self): - return self.memDB.series_service - - def _populate_series(self): - # [(ID, value, datetime), ...] - #self._cursor.execute("SELECT ValueID, DataValue, LocalDateTime FROM DataValues ORDER BY LocalDateTime") - - self._series_points_df = self.memDB.getDataValuesDF() - - - def _test_filter_previous(self): - - ''' - if not self._filter_from_selection: - self.reset_filter() - ''' - - df = None - - if not self._filter_from_selection: - df = self._series_points_df - else: - df = self.filtered_dataframe - - # Ensure that we're not working with an empty dataframe - - if isinstance(df, pd.DataFrame): - if df.empty: - return self._series_points_df - else: - if not df: - return self._series_points_df - - return df - - def datetime2dataframe(self, datetime_list): - """ Converts datetime_list to a pandas Dataframe - - - :param datetime_list: - :return Pandas.DataFrame: - """ - - result = None - - if isinstance(datetime_list, list): - - result = pd.DataFrame(datetime_list, columns=["LocalDateTime"]) - - result.set_index("LocalDateTime", inplace=True) - - return result - - ################### - # Stubs - ################### - def selectPointsStub(self): - """ - :param filtered_dataframe: - :return: - """ - - ## Convert dataframe into list of datetimes - - filtered_dataframe = self.get_filtered_points() - if isinstance(filtered_dataframe, pd.DataFrame): - if not filtered_dataframe.empty: - datetime_list = filtered_dataframe.index.to_pydatetime() - return datetime_list.tolist() - return [] - - ################### - # Filters - ################### - # operator is a character, either '<' or '>' - def filter_value(self, value, ops): - df = self._test_filter_previous() - - if ops == '>': - self.filtered_dataframe = df[df['DataValue'] > value] - - if ops == '<': - self.filtered_dataframe = df[df['DataValue'] < value] - - - def filter_date(self, before, after): - df = self._test_filter_previous() - if before and after: - self.filtered_dataframe = df[(df.index < before) & (df.index > after)] - - # Data Gaps - def data_gaps(self, value, time_period): - df = self._test_filter_previous() - - time_units = { - 'second': 's', - 'minute': 'm', - 'hour': 'h', - 'day': 'D', - 'week': 'W', - 'month': 'M', - 'year': 'Y' - } - - # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps - copy_df = df - copy_df['datetime'] = df.index - copy_df['dateprev'] = copy_df['datetime'].shift() - - # ensure that 'value' is an integer - if not isinstance(value, int): - value = int(value) - - # create a bool column indicating which rows meet condition - filtered_results = copy_df['datetime'].diff() >= np.timedelta64(value, time_units[time_period]) - - # filter on rows that passed previous condition - copy_df = copy_df[filtered_results] - - # merge values and remove duplicates. this hack allows for both values to be marked when selecting data gaps - newdf = pd.concat([copy_df['datetime'], copy_df['dateprev']], join='inner') - self.filtered_dataframe = df[df.index.isin(newdf.drop_duplicates().dropna())] - - # clean up - del copy_df - del filtered_results - del newdf - - def change_value_threshold(self, value, operator): - - df = self._test_filter_previous() - - # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps - copy_df = df - copy_df['values'] = df['DataValue'] - copy_df['diff'] = copy_df['values'].shift() - copy_df["diff_date"] = copy_df['LocalDateTime'].shift() - copy_df['change_threshold'] = abs(df['values'] - df['diff']) - - if not isinstance(value, float): - logger.error("Need to have a float") - return - - copy_df['threshold'] = value - - if operator == ">": - copy_df['matches'] = df['change_threshold'] >= copy_df['threshold'] - - if operator == "<": - copy_df['matches'] = df['change_threshold'] <= copy_df['threshold'] - - filtered_df = copy_df[copy_df['matches']] - tmplist = filtered_df['diff_date'].tolist() + filtered_df.index.tolist() - del copy_df - self.filtered_dataframe = df[df.index.isin(tmplist)] - - - def select_points_tf(self, tf_list): - self._filter_list = tf_list - - #def select_points(self, id_list=[], datetime_list=[]): - def select_points(self, id_list=[], dataframe=[]): - #self.reset_filter() - - # This should be either one or the other. If it's both, id is used first. - # If neither are set this function does nothing. - - if len(id_list) > 0: - for i in range(len(self._series_points)): - if self._series_points[i][0] in id_list: - self._filter_list[i] = True - - if isinstance(dataframe, pd.DataFrame): - result = dataframe.index.astype(datetime.datetime) - self.filtered_dataframe = self._series_points_df[self._series_points_df.index.isin(dataframe.index)] - - - def reset_filter(self): - self.filtered_dataframe = None - - def filter_from_previous(self, value): - self._filter_from_selection = value - - def get_toggle(self): - return self._filter_from_selection - - - ################### - # Gets - ################### - def get_series(self): - return self.memDB.series_service.get_series_by_id(self._series_id) - - def get_series_points(self): - # all point in the series - return self._series_points - - def get_series_points_df(self): - """ - :return Pandas DataFrame: - """ - return self._series_points_df - - def get_filtered_points(self): - """ - :return Pandas DataFrame: - """ - if isinstance(self.filtered_dataframe, pd.DataFrame): - if self.filtered_dataframe.empty: - return None - else: - if not self.filtered_dataframe: - return None - if len(self.filtered_dataframe) > 0: - return self.filtered_dataframe - return None - - def get_filtered_dates(self): - return self.filtered_dataframe - - def get_filter_list(self): - # true or false list the length of the entire series. true indicate the point is selected - return self._filter_list - - def get_qcl(self, qcl_id): - return self.memDB.series_service.get_qcl_by_id(qcl_id) - - def get_method(self, method_id): - return self.memDB.series_service.get_method_by_id(method_id) - - def get_variable(self, variable_id): - logger.debug(variable_id) - return self.memDB.series_service.get_variable_by_id(variable_id) - - - ################# - # Edits - ################# - - def change_value(self, value, operator): - filtered_points = self.get_filtered_points() - - ids = filtered_points.index.tolist() - self.memDB.updateValue(ids, operator, float(value)) - self._populate_series() - - ## update filtered_dataframe - self.filtered_dataframe = self._series_points_df[self._series_points_df.index.isin(ids)] - - def add_points(self, points): - # todo: add the ability to send in multiple datetimes to a single 'point' - - self.memDB.addPoints(points) - - self._populate_series() - self.reset_filter() - - def delete_points(self): - filtered_points = self.get_filtered_points() - if not filtered_points.empty: - values = filtered_points.index.tolist() - - self.memDB.delete(values) - self._populate_series() - self.filtered_dataframe = None - - def interpolate(self): - ''' - In [75]: ser = Series(np.sort(np.random.uniform(size=100))) - # interpolate at new_index - In [76]: new_index = ser.index | Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75]) - In [77]: interp_s = ser.reindex(new_index).interpolate(method='pchip') - ''' - - tmp_filter_list =self.get_filtered_points() - df = self._series_points_df - issel = df.index.isin(tmp_filter_list.index) - - mdf = df["DataValue"].mask(issel) - mdf.interpolate(method = "time", inplace=True) - tmp_filter_list["DataValue"]=mdf[issel] - ids = tmp_filter_list.index.tolist() - - #update_list = [(row["DataValue"], row["ValueID"]) for index, row in tmp_filter_list.iterrows()] - update_list = [{"value": row["DataValue"], "id": index} for index, row in tmp_filter_list.iterrows()] - - self.memDB.update(update_list) - - - self._populate_series() - - self.filtered_dataframe = self._series_points_df[self._series_points_df.index.isin(ids)] - - def drift_correction(self, gap_width): - if self.isOneGroup(): - tmp_filter_list =self.get_filtered_points() - startdate =tmp_filter_list.index[0] - x_l = (tmp_filter_list.index[-1]-startdate).total_seconds() - - # y_n = y_0 + G(x_i / x_l) - f = lambda row : row["DataValue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) - tmp_filter_list["DataValue"]=tmp_filter_list.apply(f, axis = 1) - - update_list = [{"value": row["DataValue"], "id":index} for index, row in tmp_filter_list.iterrows()] - - ids = tmp_filter_list.index.tolist() - self.memDB.update(update_list) - - - self._populate_series() - - self.filtered_dataframe = self._series_points_df[self._series_points_df.index.isin(ids)] - return True - return False - - def isOneGroup(self): - - issel = self._series_points_df.index.isin(self.get_filtered_points().index) - - found_group = False - count = 0 - - for x in issel: - if x: - if not found_group: - found_group=True - count =count+1 - else: - found_group = False - - if count >1: - return False - if count == 1: - return True - - - def flag(self, qualifier_id): - - filtered_points = self.get_filtered_points() - ''' - query = "UPDATE DataValues SET QualifierID = %s WHERE ValueID = ?" % (qualifier_id) - #self._cursor.executemany(query, [(str(x[0]),) for x in filtered_points]) - self._cursor.executemany(query, [(str(x),) for x in filtered_points["ValueID"].astype(int).tolist()]) - ''' - self.memDB.updateFlag(filtered_points.index.astype(int).tolist(), qualifier_id) - - ################### - # Save/Restore - ################### - - def restore(self): - self.memDB.rollback() - - self._populate_series() - self.reset_filter() - - def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False): - """ - - :param var: - :param method: - :param qcl: - :param is_new_series: - :return: - """ - - var_id = var.id if var is not None else None - method_id = method.id if method is not None else None - qcl_id = qcl.id if qcl is not None else None - #self.memDB.changeSeriesIDs(var_id, method_id, qcl_id) - dvs = self.memDB.getDataValuesDF() - if var_id is not None: - dvs["VariableID"] = var_id - if method_id is not None: - dvs["MethodID"] = method_id - if qcl_id is not None: - dvs["QualityControlLevelID"] = qcl_id - - - - #if is new series remove valueids - #if is_new_series: - dvs["ValueID"] = None - ''' - for dv in dvs: - dv.id = None - ''' - - series = self.memDB.series_service.get_series_by_id(self._series_id) - logger.debug("original editing series id: %s" % str(series.id)) - - if (var or method or qcl ): - tseries = self.memDB.series_service.get_series_by_id_quint(site_id=int(series.site_id), - var_id=var_id if var else int(series.variable_id), - method_id=method_id if method else int( - series.method_id), - source_id=series.source_id, - qcl_id=qcl_id if qcl else int( - series.quality_control_level_id)) - if tseries: - logger.debug("Save existing series ID: %s" % str(series.id)) - series = tseries - else: - print "Series doesn't exist (if you are not, you should be running SaveAs)" - - if is_new_series: - - - series = series_module.copy_series(series) - - - if var: - series.variable_id = var_id - series.variable_code = var.code - series.variable_name = var.name - series.speciation = var.speciation - series.variable_units_id = var.variable_unit_id - series.variable_units_name = var.variable_unit.name - series.sample_medium = var.sample_medium - series.value_type = var.value_type - series.time_support = var.time_support - series.time_units_id = var.time_unit_id - series.time_units_name = var.time_unit.name - series.data_type = var.data_type - series.general_category = var.general_category - - if method: - series.method_id = method_id - series.method_description = method.description - - if qcl: - series.quality_control_level_id = qcl_id - series.quality_control_level_code = qcl.code - ''' - dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) - dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) - ''' - - - - - - form = "%Y-%m-%d %H:%M:%S" - series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[0].local_date_time - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time - series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc - series.value_count = len(dvs) - - ## Override previous save - if not is_new_series: - # delete old dvs - #pass - self.memDB.series_service.delete_values_by_series(series) - - - #logger.debug("series.data_values: %s" % ([x for x in series.data_values])) - dvs.drop('ValueID', axis=1, inplace=True) - return series, dvs - - def save(self): - """ Save to an existing catalog - :param var: - :param method: - :param qcl: - :return: - """ - - series, dvs = self.updateSeries(is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save was unsuccessful") - return False - - def save_as(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) - - if self.memDB.series_service.save_new_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save As Function was Unsuccessful") - return False - - def save_existing(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save As Existing Function was Unsuccessful") - return False - - def create_qcl(self, code, definition, explanation): - return self.memDB.series_service.create_qcl(code, definition, explanation) - - def create_method(self, description, link): - return self.memDB.series_service.create_method(description, link) - - def create_qualifier(self, code, definition): - return self.memDB.series_service.create_qualifier(code, definition) - - def create_variable(self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, general_category, no_data_value): - - return self.memDB.series_service.create_variable(code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value) - - def reconcile_dates(self, parent_series_id): - # FUTURE FEATURE: pull in new field data from another series and add to this series - # (i.e one series contains new field data of an edited series at a higher qcl) - pass - - diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py deleted file mode 100644 index 7c8ccc4..0000000 --- a/odmtools/odmservices/series_service.py +++ /dev/null @@ -1,782 +0,0 @@ -import logging - - -from sqlalchemy import distinct, func - - -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import Site -from odmtools.odmdata import Variable -from odmtools.odmdata import Unit -from odmtools.odmdata import Series -from odmtools.odmdata import DataValue -from odmtools.odmdata import Qualifier -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Method -from odmtools.odmdata import QualityControlLevel -from odmtools.odmdata import ODMVersion -from odmtools.common.logger import LoggerTool -import pandas as pd - -tool = LoggerTool() -logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) - - -class SeriesService(): - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug - - def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks - - def get_db_version(self): - return self._edit_session.query(ODMVersion).first().version_number - -##################### -# -# Get functions -# -##################### - - # Site methods - def get_all_sites(self): - """ - - :return: List[Sites] - """ - return self._edit_session.query(Site).order_by(Site.code).all() - - - def get_used_sites(self): - """ - Return a list of all sites that are being referenced in the Series Catalog Table - :return: List[Sites] - """ - try: - site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] - except: - site_ids = None - - if not site_ids: - return None - - Sites = [] - for site_id in site_ids: - Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) - - return Sites - - - def get_site_by_id(self, site_id): - """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites - """ - try: - return self._edit_session.query(Site).filter_by(id=site_id).first() - except: - return None - - # Variables methods - def get_used_variables(self): - """ - #get list of used variable ids - :return: List[Variables] - """ - - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] - except: - var_ids = None - - if not var_ids: - return None - - Variables = [] - - #create list of variables from the list of ids - for var_id in var_ids: - Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return Variables - - def get_all_variables(self): - """ - - :return: List[Variables] - """ - return self._edit_session.query(Variable).all() - - def get_variable_by_id(self, variable_id): - """ - - :param variable_id: int - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() - except: - return None - - def get_variable_by_code(self, variable_code): - """ - - :param variable_code: str - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() - except: - return None - - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] - """ - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( - site_code=site_code).all()] - except: - var_ids = None - - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return variables - - # Unit methods - def get_all_units(self): - """ - - :return: List[Units] - """ - return self._edit_session.query(Unit).all() - - def get_unit_by_name(self, unit_name): - """ - - :param unit_name: str - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None - - def get_unit_by_id(self, unit_id): - """ - - :param unit_id: int - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() - except: - return None - - - def get_all_qualifiers(self): - """ - - :return: List[Qualifiers] - """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result - - def get_qualifiers_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() - - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() - - def get_qcl_by_id(self, qcl_id): - try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() - except: - return None - - def get_qcl_by_code(self, qcl_code): - try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() - except: - return None - - # Method methods - def get_all_methods(self): - return self._edit_session.query(Method).all() - - def get_method_by_id(self, method_id): - try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() - except: - result = None - return result - - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() - except: - result = None - return result - - def get_offset_types_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() - return self._edit_session.query(OffsetType).join(subquery).distinct().all() - - def get_samples_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.sample_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() - return self._edit_session.query(Sample).join(subquery).distinct().all() - - # Series Catalog methods - def get_all_series(self): - """ - Returns all series as a modelObject - :return: List[Series] - """ - - #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) - return self._edit_session.query(Series).order_by(Series.id).all() - - def get_series_by_site(self , site_id): - """ - - :param site_id: int - :return: List[Series] - """ - try: - selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() - return selectedSeries - except: - return None - - def get_series_by_id(self, series_id): - """ - - :param series_id: int - :return: Series - """ - try: - return self._edit_session.query(Series).filter_by(id=series_id).first() - except Exception as e: - print e - return None - - def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: Series - """ - try: - return self._edit_session.query(Series).filter_by( - site_id=site_id, variable_id=var_id, method_id=method_id, - source_id=source_id, quality_control_level_id=qcl_id).first() - except: - return None - - def get_series_from_filter(self): - # Pass in probably a Series object, match it against the database - pass - - - #Data Value Methods - def get_values_by_series(self, series_id): - ''' - - :param series_id: Series id - :return: pandas dataframe - ''' - series= self.get_series_by_id(series_id) - if series: - q = self._edit_session.query(DataValue).filter_by( - site_id=series.site_id, - variable_id=series.variable_id, - method_id=series.method_id, - source_id=series.source_id, - quality_control_level_id=series.quality_control_level_id) - - query=q.statement.compile(dialect=self._session_factory.engine.dialect) - data= pd.read_sql_query(sql= query, - con = self._session_factory.engine, - params = query.params ) - #return data.set_index(data['LocalDateTime']) - return data - else: - return None - - def get_all_values_df(self): - """ - - :return: Pandas DataFrame object - """ - q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) - columns = list(data) - - columns.insert(0, columns.pop(columns.index("DataValue"))) - columns.insert(1, columns.pop(columns.index("LocalDateTime"))) - columns.insert(2, columns.pop(columns.index("QualifierID"))) - - data = data.ix[:, columns] - return data.set_index(data['LocalDateTime']) - - def get_all_values_list(self): - """ - - :return: - """ - result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - return [x.list_repr() for x in result] - - def get_all_values(self): - return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - - @staticmethod - def calcSeason(row): - - month = int(row["Month"]) - - if month in [1, 2, 3]: - return 1 - elif month in[4, 5, 6]: - return 2 - elif month in [7, 8, 9]: - return 3 - elif month in [10, 11, 12]: - return 4 - - def get_all_plot_values(self): - """ - - :return: - """ - q = self._edit_session.query(DataValue.data_value.label('DataValue'), - DataValue.local_date_time.label('LocalDateTime'), - DataValue.censor_code.label('CensorCode'), - func.strftime('%m', DataValue.local_date_time).label('Month'), - func.strftime('%Y', DataValue.local_date_time).label('Year') - #DataValue.local_date_time.strftime('%m'), - #DataValue.local_date_time.strftime('%Y')) - ).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data.set_index(data['LocalDateTime']) - - def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): - """ - - :param seriesID: - :param noDataValue: - :param startDate: - :param endDate: - :return: - """ - series = self.get_series_by_id(seriesID) - - DataValues = [ - (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), - dv.local_date_time.strftime('%Y')) - for dv in series.data_values - if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate - ] - data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) - data.set_index(data['LocalDateTime'], inplace=True) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data - - - - def get_data_value_by_id(self, id): - """ - - :param id: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(id=id).first() - except: - return None - - - - -##################### -# -#Update functions -# -##################### - def update_series(self, series): - """ - - :param series: - :return: - """ - merged_series = self._edit_session.merge(series) - self._edit_session.add(merged_series) - self._edit_session.commit() - - def update_dvs(self, dv_list): - """ - - :param dv_list: - :return: - """ - merged_dv_list = map(self._edit_session.merge, dv_list) - self._edit_session.add_all(merged_dv_list) - self._edit_session.commit() - -##################### -# -#Create functions -# -##################### - def save_series(self, series, dvs): - """ Save to an Existing Series - :param series: - :param data_values: - :return: - """ - - if self.series_exists(series): - - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - logger.debug("Existing File was overwritten with new information") - return True - else: - logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") - # there wasn't an existing file to overwrite - raise Exception("Series does not exist, unable to save. Please select 'Save As'") - - - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.debug(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - - logger.debug("A new series was added to the database, series id: "+str(series.id)) - return True - - def save_values(self, values): - """ - - :param values: pandas dataframe - :return: - """ - values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) - - - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - """ - - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Series() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - - self._edit_session.add(series) - self._edit_session.commit() - return series - - def create_method(self, description, link): - """ - - :param description: - :param link: - :return: - """ - meth = Method() - meth.description = description - if link is not None: - meth.link = link - - self._edit_session.add(meth) - self._edit_session.commit() - return meth - - def create_variable_by_var(self, var): - """ - - :param var: Variable Object - :return: - """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None - - def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): - """ - - :param code: - :param name: - :param speciation: - :param variable_unit_id: - :param sample_medium: - :param value_type: - :param is_regular: - :param time_support: - :param time_unit_id: - :param data_type: - :param general_category: - :param no_data_value: - :return: - """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - - self._edit_session.add(var) - self._edit_session.commit() - return var - - def create_qcl(self, code, definition, explanation): - """ - - :param code: - :param definition: - :param explanation: - :return: - """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - - self._edit_session.add(qcl) - self._edit_session.commit() - return qcl - - - def create_qualifier_by_qual(self, qualifier): - self._edit_session.add(qualifier) - self._edit_session.commit() - return qualifier - - def create_qualifier(self, code, description): - """ - - :param code: - :param description: - :return: - """ - qual = Qualifier() - qual.code = code - qual.description = description - - return self.create_qualifier_by_qual(qual) - -##################### -# -# Delete functions -# -##################### - - def delete_series(self, series): - """ - - :param series: - :return: - """ - self.delete_values_by_series(series) - - delete_series = self._edit_session.merge(series) - self._edit_session.delete(delete_series) - self._edit_session.commit() - - - def delete_values_by_series(self, series): - """ - - :param series: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - variable_id = series.variable_id, - method_id = series.method_id, - source_id = series.source_id, - quality_control_level_id = series.quality_control_level_id).delete() - except: - return None - - def delete_dvs(self, id_list): - """ - - :param id_list: list of ids - :return: - """ - self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) - -##################### -# -#Exist functions -# -##################### - - - def series_exists(self, series): - """ - - :param series: - :return: - """ - return self.series_exists_quint( - series.site_id, - series.variable_id, - series.method_id, - series.source_id, - series.quality_control_level_id - ) - - def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - try: - result = self._edit_session.query(Series).filter_by( - site_id=site_id, - variable_id=var_id, - method_id=method_id, - source_id=source_id, - quality_control_level_id=qcl_id - ).one() - - return True - except: - return False - def qcl_exists(self, q): - """ - - :param q: - :return: - """ - try: - result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() - return True - except: - - return False - - def method_exists(self, m): - """ - - :param m: - :return: - """ - try: - result = self._edit_session.query(Method).filter_by(description=m.description).one() - return True - except: - return False - - def variable_exists(self, v): - """ - - :param v: - :return: - """ - try: - result = self._edit_session.query(Variable).filter_by(code=v.code, - name=v.name, speciation=v.speciation, - variable_unit_id=v.variable_unit_id, - sample_medium=v.sample_medium, - value_type=v.value_type, is_regular=v.is_regular, - time_support=v.time_support, - time_unit_id=v.time_unit_id, data_type=v.data_type, - general_category=v.general_category, - no_data_value=v.no_data_value).one() - return result - except: - return None \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py new file mode 100755 index 0000000..e8159ea --- /dev/null +++ b/odmtools/odmservices/service_manager.py @@ -0,0 +1,208 @@ +import logging +import os +import sys + +import urllib + +from sqlalchemy.exc import SQLAlchemyError +from odmtools.odmservices import SeriesService, CVService, EditService, ExportService +from odmtools.controller import EditTools +from odmtools.lib.Appdirs.appdirs import user_config_dir +from odmtools.odmdata import SessionFactory + + + +class ServiceManager(): + def __init__(self, debug=False): + self.debug = debug + f = self._get_file('r') + self._conn_dicts = [] + self.version = 0 + self._connection_format = "%s+%s://%s:%s@%s/%s" + + # Read all lines (connections) in the connection.cfg file + while True: + line = f.readline() + if not line: + break + else: + line = line.split() + #logger.debug(line) + + if len(line) >= 5: + line_dict = {} + + line_dict['engine'] = line[0] + line_dict['user'] = line[1] + line_dict['password'] = line[2] + line_dict['address'] = line[3] + line_dict['db'] = line[4] + self._conn_dicts.append(line_dict) + + if len(self._conn_dicts) is not 0: + # The current connection defaults to the most recent (i.e. the last written to the file) + self._current_conn_dict = self._conn_dicts[-1] + else: + self._current_conn_dict = None + + f.close() + + def get_all_conn_dicts(self): + return self._conn_dicts + + def is_valid_connection(self): + if self._current_conn_dict: + conn_string = self._build_connection_string(self._current_conn_dict) + logger.debug("Conn_string: %s" % conn_string) + try: + if self.testEngine(conn_string): + return self.get_current_conn_dict() + except Exception as e: + logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) + return None + return None + + def get_current_conn_dict(self): + return self._current_conn_dict + + def set_current_conn_dict(self, dict): + self._current_conn_dict = dict + + def add_connection(self, conn_dict): + """conn_dict must be a dictionary with keys: engine, user, password, address, db""" + + # remove earlier connections that are identical to this one + self.delete_connection(conn_dict) + + if self.test_connection(conn_dict): + # write changes to connection file + self._conn_dicts.append(conn_dict) + self._current_conn_dict = self._conn_dicts[-1] + self._save_connections() + return True + else: + logger.error("Unable to save connection due to invalid connection to database") + return False + + + @classmethod + def testEngine(self, connection_string): + s = SessionFactory(connection_string, echo=False) + if 'mssql' in connection_string: + s.ms_test_Session().execute("Select top 1 VariableCode From Variables") + elif 'mysql' in connection_string: + s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') + elif 'postgresql' in connection_string: + #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') + s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + return True + + def test_connection(self, conn_dict): + try: + conn_string = self._build_connection_string(conn_dict) + if self.testEngine(conn_string) and self.get_db_version(conn_string) == '1.1.1': + return True + except SQLAlchemyError as e: + logger.error("SQLAlchemy Error: %s" % e.message) + raise e + except Exception as e: + logger.error("Error: %s" % e) + raise e + return False + + def delete_connection(self, conn_dict): + self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] + + # Create and return services based on the currently active connection + def get_db_version_dict(self, conn_dict): + conn_string = self._build_connection_string(conn_dict) + self.get_db_version(conn_string) + + def get_db_version(self, conn_string): + if isinstance(conn_string, dict): + conn_string = self._build_connection_string(conn_string) + service = SeriesService(conn_string) + #if not self.version: + try: + self.version = service.get_db_version() + except Exception as e: + logger.error("Exception: %s" % e.message) + return None + return self.version + + def get_series_service(self, conn_dict=""): + conn_string = "" + if conn_dict: + conn_string = self._build_connection_string(conn_dict) + self._current_conn_dict = conn_dict + else: + conn_string = self._build_connection_string(self._current_conn_dict) + return SeriesService(conn_string, self.debug) + + def get_cv_service(self): + conn_string = self._build_connection_string(self._current_conn_dict) + return CVService(conn_string, self.debug) + + def get_edit_service(self, series_id, connection): + + return EditService(series_id, connection=connection, debug=self.debug) + + def get_record_service(self, script, series_id, connection): + return EditTools(self, script, self.get_edit_service(series_id, connection), + self._build_connection_string(self.is_valid_connection())) + + def get_export_service(self): + return ExportService(self.get_series_service()) + + ## ################### + # private variables + ## ################### + + def _get_file(self, mode): + #fn = util.resource_path('connection.config') + fn = os.path.join(user_config_dir("ODMTools", "UCHIC"), 'connection.config') + + config_file = None + try: + + if os.path.exists(fn): + config_file = open(fn, mode) + else: + os.makedirs(user_config_dir("ODMTools", "UCHIC")) + open(fn, 'w').close() + config_file = open(fn, mode) + except: + open(fn, 'w').close() + config_file = open(fn, mode) + + + return config_file + + def _build_connection_string(self, conn_dict): + driver = "" + if conn_dict['engine'] == 'mssql' and sys.platform != 'win32': + driver = "pyodbc" + quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;' % (conn_dict['address'], conn_dict['user'], conn_dict['password'])) + conn_string = 'mssql+pyodbc:///?odbc_connect={}'.format(quoted) + + else: + if conn_dict['engine'] == 'mssql': + driver = "pyodbc" + elif conn_dict['engine'] == 'mysql': + driver = "pymysql" + elif conn_dict['engine'] == 'postgresql': + driver = "psycopg2" + else: + driver = "None" + + conn_string = self._connection_format % ( + conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], + conn_dict['db']) + return conn_string + + def _save_connections(self): + f = self._get_file('w') + for conn in self._conn_dicts: + f.write("%s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'])) + f.close() + From d4e8fef9c01ee1d6848d97762ae519ec4d1a5f51 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Wed, 26 Aug 2015 13:32:27 -0600 Subject: [PATCH 004/158] update odmdata import --- odmtools/controller/olvSeriesSelector.py | 2 +- odmtools/controller/pageExisting.py | 2 +- odmtools/odmdata/__init__.py | 67 ++++++++++-------------- odmtools/odmdata/memory_database.py | 7 ++- odmtools/odmservices/__init__.py | 5 +- odmtools/odmservices/service_manager.py | 27 +++++----- 6 files changed, 49 insertions(+), 61 deletions(-) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 7f21f32..0a9a4f1 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -48,7 +48,7 @@ def _buildColumns(self): ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in series.returnDict().iteritems()] + for key, value in Series.returnDict().iteritems()] self.SetColumns(seriesColumns) """User can select series using the mouse to click on check boxes """ diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index e7c8318..026cedd 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -2,7 +2,7 @@ import wx from odmtools.view import clsExisting -from odmtools.odmdata import series +from odmtools.odmdata import Series import wx.wizard as wiz import datetime diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 447f960..27362a8 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,35 +1,4 @@ ''' -from base import Base -from censor_code_cv import CensorCodeCV -from data_type_cv import DataTypeCV -from data_value import DataValue -from general_category_cv import GeneralCategoryCV -from iso_metadata import ISOMetadata -from lab_method import LabMethod -from method import Method -from odm_version import ODMVersion -from offset_type import OffsetType -from qualifier import Qualifier -from quality_control_level import QualityControlLevel -from sample import Sample -from sample_medium_cv import SampleMediumCV -from sample_type_cv import SampleTypeCV -from series import Series -from session_factory import SessionFactory -from site import Site -from site_type_cv import SiteTypeCV -from source import Source -from spatial_reference import SpatialReference -from speciation_cv import SpeciationCV -from topic_category_cv import TopicCategoryCV -from unit import Unit -from value_type_cv import ValueTypeCV -from variable import Variable -from variable_name_cv import VariableNameCV -from vertical_datum_cv import VerticalDatumCV - -from series import copy_series -from data_value import copy_data_value __all__ = [ 'Base', @@ -61,22 +30,40 @@ 'VariableNameCV', 'VerticalDatumCV', 'MemoryDatabase', - 'copy_series', - 'copy_data_value' + ] ''' -import sys -from odmtools.odmdata import memory_database - -from src.api.ODMconnection import SessionFactory -from src.api.versionSwitcher.ODM import DataTypeCV, DataValue, GeneralCategoryCV, ISOMetadata, LabMethod, Method, \ - OffsetType,Qualifier,QualityControlLevel,Sample,SampleMediumCV,SampleTypeCV, Series, Site,SiteTypeCV,Source, \ - SpatialReference,SpeciationCV,TopicCategoryCV,Unit,ValueTypeCV,Variable,VerticalDatumCV +from api.ODMconnection import SessionFactory +from api.versionSwitcher import ODM +DataTypeCV=ODM.DataTypeCV +DataValue=ODM.DataValue +# GeneralCategoryCV=ODM.GeneralCategoryCV +ISOMetadata=ODM.ISOMetadata +LabMethod=ODM.LabMethod +Method=ODM.Method +OffsetType=ODM.OffsetType +Qualifier=ODM.Qualifier +QualityControlLevel=ODM.QualityControlLevel +Sample =ODM.Sample +SampledMediumCV= ODM.SampleMediumCV +# SampleTypeCV=ODM.SampleTypeCV +Series=ODM.Series +Site= ODM.Site +SiteType=ODM.SiteTypeCV +Source =ODM.Source +SpatialReferences=ODM.SpatialReference +SpeciationCV=ODM.SpeciationCV +# TopicCategoryCV=ODM.TopicCategoryCV +Unit= ODM.Unit +# ValueTypeCV=ODM.ValueTypeCV +Variable = ODM.Variable +VerticalDatumCV=ODM.VerticalDatumCV +from odmtools.odmdata.memory_database import MemoryDatabase __all__=[ 'SessionFactory', diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 00b1156..8c0a526 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -4,6 +4,7 @@ from odmtools.common.logger import LoggerTool from odmtools.odmservices import SeriesService +from odmtools.odmservices import ServiceManager from odmtools.odmdata import DataValue tool = LoggerTool() @@ -21,7 +22,8 @@ def __init__(self, taskserver=None): # Series_Service handles remote database self.series_service = None # Memory_service handles in memory database - self.mem_service = SeriesService("sqlite:///:memory:") + sm = ServiceManager() + self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") # TODO clean up closing of program # if taskserver is None: #numproc = cpu_count() @@ -31,7 +33,8 @@ def __init__(self, taskserver=None): self.taskserver = taskserver def reset_edit(self): - self.mem_service = SeriesService("sqlite:///:memory:") + sm = ServiceManager() + self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") def set_series_service(self, service): self.series_service = service diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index c850d5c..23dd7c6 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -8,9 +8,8 @@ # import pymysql # import pyodbc # #import psycopg2 -import sys -sys.path.append('/Users/stephanie/DEV/ODM2PythonAPI/') -from src.api.ODM1_1_1.services import SeriesService, EditService, CVService, ExportService + +from api.ODM1_1_1.services import SeriesService, EditService, CVService, ExportService from service_manager import ServiceManager __all__ = [ diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index e8159ea..6348981 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,8 +8,11 @@ from odmtools.odmservices import SeriesService, CVService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SessionFactory +from odmtools.odmdata import SessionFactory, Variable +from odmtools.common.logger import LoggerTool +tool = LoggerTool() +logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) class ServiceManager(): @@ -88,13 +91,9 @@ def add_connection(self, conn_dict): @classmethod def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) - if 'mssql' in connection_string: - s.ms_test_Session().execute("Select top 1 VariableCode From Variables") - elif 'mysql' in connection_string: - s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') - elif 'postgresql' in connection_string: - #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + + s.test_Session().query(Variable.code).limit(1).first() + return True def test_connection(self, conn_dict): @@ -130,22 +129,22 @@ def get_db_version(self, conn_string): return None return self.version - def get_series_service(self, conn_dict=""): - conn_string = "" + def get_series_service(self, conn_dict="", conn_string = ""): + if conn_dict: conn_string = self._build_connection_string(conn_dict) self._current_conn_dict = conn_dict - else: + elif not conn_string: conn_string = self._build_connection_string(self._current_conn_dict) - return SeriesService(conn_string, self.debug) + return SeriesService(SessionFactory(conn_string, self.debug)) def get_cv_service(self): conn_string = self._build_connection_string(self._current_conn_dict) - return CVService(conn_string, self.debug) + return CVService(SessionFactory(conn_string, self.debug)) def get_edit_service(self, series_id, connection): - return EditService(series_id, connection=connection, debug=self.debug) + return EditService(series_id, connection, debug=self.debug) def get_record_service(self, script, series_id, connection): return EditTools(self, script, self.get_edit_service(series_id, connection), From f96c12488999b32408c40f5783d46792926cf3c7 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 1 Sep 2015 15:48:55 -0600 Subject: [PATCH 005/158] make adjustments to work with api --- odmtools/controller/logicPlotOptions.py | 3 +- odmtools/controller/olvSeriesSelector.py | 4 +- odmtools/gui/frmODMTools.py | 11 ++- odmtools/odmdata/__init__.py | 5 +- odmtools/odmdata/memory_database.py | 14 +-- odmtools/odmservices/service_manager.py | 114 ++++++++++++++++------- 6 files changed, 101 insertions(+), 50 deletions(-) diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 9da1f6f..5bf4e31 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -219,7 +219,8 @@ def getSeriesById(self, seriesID): series = self.memDB.series_service.get_series_by_id(seriesID) self.memDB.series_service.reset_session() return series - except: + except Exception as e: + print e return None def getSelectedSeries(self, seriesID): diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 0a9a4f1..8002d8d 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -6,7 +6,7 @@ from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn from odmtools.common.logger import LoggerTool -from odmtools.odmdata import Series +from odmtools.odmdata import ODM tool = LoggerTool() @@ -48,7 +48,7 @@ def _buildColumns(self): ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in Series.returnDict().iteritems()] + for key, value in ODM.returnDict().iteritems()] self.SetColumns(seriesColumns) """User can select series using the mouse to click on check boxes """ diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index 91c3571..c5e9b30 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -122,11 +122,12 @@ def _init_database(self, quit_if_cancel=True): while True: ## Database connection is valid, therefore proceed through the rest of the program - if self.service_manager.is_valid_connection(): - conn_dict = None + conn_dict = self.service_manager.is_valid_connection() + if conn_dict: + #conn_dict = None - series_service = self.createService() - conn_dict = self.service_manager.get_current_conn_dict() + series_service = self.createService(conn_dict) + #conn_dict = self.service_manager.get_current_conn_dict() if self.servicesValid(series_service): self.service_manager.add_connection(conn_dict) @@ -458,7 +459,7 @@ def createService(self, conn_dict=""): :return: """ - series_service = self.service_manager.get_series_service(conn_dict=conn_dict) + series_service= self.service_manager.get_series_service(conn_dict=conn_dict)#=connection) return series_service def getServiceManager(self): diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 27362a8..a4d447e 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -38,7 +38,8 @@ from api.ODMconnection import SessionFactory -from api.versionSwitcher import ODM +from api.versionSwitcher import ODM, refreshDB +from api.ODM2.models import change_schema DataTypeCV=ODM.DataTypeCV DataValue=ODM.DataValue # GeneralCategoryCV=ODM.GeneralCategoryCV @@ -94,6 +95,8 @@ 'Variable', 'VariableNameCV', 'VerticalDatumCV', + 'refreshDB', + 'change_schema', 'MemoryDatabase', diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 8c0a526..194a632 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -85,10 +85,10 @@ def getEditDataValuesforGraph(self): return self.mem_service.get_all_plot_values() def commit(self): - self.mem_service._edit_session.commit() + self.mem_service._session.commit() def rollback(self): - self.mem_service._edit_session.rollback() + self.mem_service._session.rollback() # self.mem_service._session_factory.engine.connect().connection.rollback() #self.updateDF() @@ -102,7 +102,7 @@ def update(self, updates): values(DataValue=bindparam('value')) ) - self.mem_service._edit_session.execute(stmt, updates) + self.mem_service._session.execute(stmt, updates) # self.updateDF() @@ -122,7 +122,7 @@ def updateValue(self, ids, operator, value): #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) for c in chunks: - q=self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c)) + q=self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c)) q.update({DataValue.data_value: query}, False) #self.updateDF() @@ -134,7 +134,7 @@ def chunking(self, data): def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: - self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ + self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ .update({DataValue.qualifier_id: value}, False) @@ -163,7 +163,7 @@ def addPoints(self, points): "SiteID": point[10], "VariableID": point[11], "MethodID": point[12], "SourceID": point[13], "QualityControlLevelID": point[14]} - self.mem_service._edit_session.execute(stmt, vals) + self.mem_service._session.execute(stmt, vals) def stopEdit(self): @@ -222,7 +222,7 @@ def changeSeriesIDs(self, var=None, qcl=None, method=None): :return: """ - query = self.mem_service._edit_session.query(DataValue) + query = self.mem_service._session.query(DataValue) if var is not None: logger.debug(var) query.update({DataValue.variable_id: var}) diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 6348981..3e19a47 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,7 +8,8 @@ from odmtools.odmservices import SeriesService, CVService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SessionFactory, Variable +from odmtools.odmdata import SessionFactory, Variable, refreshDB, change_schema + from odmtools.common.logger import LoggerTool tool = LoggerTool() @@ -40,6 +41,7 @@ def __init__(self, debug=False): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] + line_dict['version']=1.1 self._conn_dicts.append(line_dict) if len(self._conn_dicts) is not 0: @@ -53,23 +55,39 @@ def __init__(self, debug=False): def get_all_conn_dicts(self): return self._conn_dicts + # def is_valid_connection(self): + # if self._current_conn_dict: + # conn_string = self._build_connection_string(self._current_conn_dict) + # logger.debug("Conn_string: %s" % conn_string) + # try: + # if self.testEngine(conn_string): + # return self._current_conn_dict + # + # return None + def is_valid_connection(self): if self._current_conn_dict: conn_string = self._build_connection_string(self._current_conn_dict) logger.debug("Conn_string: %s" % conn_string) + dbtype = self._current_conn_dict['version'] + #dbtype =1.1 + refreshDB(dbtype) + try: if self.testEngine(conn_string): - return self.get_current_conn_dict() + return self._current_conn_dict except Exception as e: logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) return None - return None + + + def get_current_conn_dict(self): return self._current_conn_dict - def set_current_conn_dict(self, dict): - self._current_conn_dict = dict + # def set_current_conn_dict(self, dict): + # self._current_conn_dict = dict def add_connection(self, conn_dict): """conn_dict must be a dictionary with keys: engine, user, password, address, db""" @@ -77,29 +95,55 @@ def add_connection(self, conn_dict): # remove earlier connections that are identical to this one self.delete_connection(conn_dict) - if self.test_connection(conn_dict): - # write changes to connection file - self._conn_dicts.append(conn_dict) - self._current_conn_dict = self._conn_dicts[-1] - self._save_connections() - return True + #assume connection has already been tested + # if self.test_connection(conn_dict): + # write changes to connection file + self._conn_dicts.append(conn_dict) + self._current_conn_dict = self._conn_dicts[-1] + self._save_connections() + return True + # else: + # logger.error("Unable to save connection due to invalid connection to database") + # return False + + + @staticmethod + def _getSchema(engine): + from sqlalchemy.engine import reflection + + insp=reflection.Inspector.from_engine(engine) + + for name in insp.get_schema_names(): + if 'odm2'== name.lower(): + return name else: - logger.error("Unable to save connection due to invalid connection to database") - return False + return insp.default_schema_name + + @classmethod + def _setSchema(self, engine): + + s = self._getSchema(engine) + change_schema(s) + @classmethod def testEngine(self, connection_string): - s = SessionFactory(connection_string, echo=False) - s.test_Session().query(Variable.code).limit(1).first() + s = SessionFactory(connection_string, echo=False) + try: + # s.ms_test_Session().query(Variable1).limit(1).first() + s.test_Session().query(Variable.code).limit(1).first() + except Exception as e: + print "Connection was unsuccessful ", e.message + return False return True def test_connection(self, conn_dict): try: conn_string = self._build_connection_string(conn_dict) - if self.testEngine(conn_string) and self.get_db_version(conn_string) == '1.1.1': + if self.testEngine(conn_string):# and self.get_db_version(conn_string) == '1.1.1': return True except SQLAlchemyError as e: logger.error("SQLAlchemy Error: %s" % e.message) @@ -113,30 +157,32 @@ def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] # Create and return services based on the currently active connection - def get_db_version_dict(self, conn_dict): - conn_string = self._build_connection_string(conn_dict) - self.get_db_version(conn_string) - - def get_db_version(self, conn_string): - if isinstance(conn_string, dict): - conn_string = self._build_connection_string(conn_string) - service = SeriesService(conn_string) - #if not self.version: - try: - self.version = service.get_db_version() - except Exception as e: - logger.error("Exception: %s" % e.message) - return None - return self.version + # def get_db_version_dict(self, conn_dict): + # conn_string = self._build_connection_string(conn_dict) + # self.get_db_version(conn_string) + # + # def get_db_version(self, conn_string): + # if isinstance(conn_string, dict): + # conn_string = self._build_connection_string(conn_string) + # service = SeriesService(conn_string) + # #if not self.version: + # try: + # self.version = service.get_db_version() + # except Exception as e: + # logger.error("Exception: %s" % e.message) + # return None + # return self.version def get_series_service(self, conn_dict="", conn_string = ""): if conn_dict: conn_string = self._build_connection_string(conn_dict) - self._current_conn_dict = conn_dict - elif not conn_string: + #self._current_conn_dict = conn_dict + elif not conn_dict and not conn_string: conn_string = self._build_connection_string(self._current_conn_dict) - return SeriesService(SessionFactory(conn_string, self.debug)) + sf = SessionFactory(conn_string, self.debug) + ss= SeriesService(sf) + return ss def get_cv_service(self): conn_string = self._build_connection_string(self._current_conn_dict) From a456718a270f4b3ca27b22a2c043cfd4906d999f Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 2 Sep 2015 14:48:57 -0600 Subject: [PATCH 006/158] add version number to db config --- odmtools/controller/frmDBConfig.py | 10 +- odmtools/controller/logicPlotOptions.py | 1 - odmtools/controller/pageExisting.py | 6 +- odmtools/gui/frmODMTools.py | 9 +- odmtools/odmdata/__init__.py | 2 + odmtools/odmservices/service_manager.py | 62 ++++---- odmtools/view/clsDBConfig.py | 187 +++++++++++++++++++++--- 7 files changed, 210 insertions(+), 67 deletions(-) diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py index 46ee811..277f1e8 100755 --- a/odmtools/controller/frmDBConfig.py +++ b/odmtools/controller/frmDBConfig.py @@ -92,10 +92,8 @@ def validateInput(self, conn_dict): wx.MessageBox(message, 'Test Connection', wx.OK) else: #TODO add error message if user cannont connect to the database ( not using VPN) but the db is still 1.1.1) - if not (self.service_manager.get_db_version(conn_dict)): - message = "Cannot connect to the database" - else: - message = "This connection is not a 1.1.1 Database" + + message = "Cannot connect to the database" wx.MessageBox(message, 'Error Occurred', wx.OK | wx.ICON_ERROR) return False @@ -108,8 +106,6 @@ def validateInput(self, conn_dict): return True - - # Returns a dictionary of the database values entered in the form def getFieldValues(self): conn_dict = {} @@ -119,6 +115,7 @@ def getFieldValues(self): conn_dict['password'] = self.txtPass.GetValue() conn_dict['address'] = self.txtServer.GetValue() conn_dict['db'] = self.txtDBName.GetValue() + conn_dict['version']= self.cbVersion.GetValue() return conn_dict @@ -128,6 +125,7 @@ def set_field_values(self): self.txtServer.SetValue(conn['address']) self.txtDBName.SetValue(conn['db']) self.txtUser.SetValue(conn['user']) + self.cbVersion.SetValue(conn['version']) for k, v in self.choices.iteritems(): if v == conn['engine']: diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 5bf4e31..d1ae4e8 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -220,7 +220,6 @@ def getSeriesById(self, seriesID): self.memDB.series_service.reset_session() return series except Exception as e: - print e return None def getSelectedSeries(self, seriesID): diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 026cedd..e8b87a8 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -2,7 +2,7 @@ import wx from odmtools.view import clsExisting -from odmtools.odmdata import Series +from odmtools.odmdata import returnDict import wx.wizard as wiz import datetime @@ -44,8 +44,6 @@ def _init_data(self, series_serv, site_id): - - # Handlers for pnlExisting events. def OnOLVItemSelected(self, event): # TODO: Implement OnOLVItemSelected @@ -62,7 +60,7 @@ def initTable(self, dbservice, site_id): seriesColumns = [clsExisting.ColumnDefn(key, align="left", minimumWidth=-1, valueGetter=value, stringConverter= '%Y-%m-%d %H:%M:%S' if 'date' in key.lower() else '%s') - for key, value in series.returnDict().iteritems()] + for key, value in returnDict().iteritems()] self.panel.olvSeriesList.SetColumns(seriesColumns) objects = dbservice.get_series_by_site(site_id= site_id) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index c5e9b30..4ac74be 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -113,7 +113,7 @@ def _init_s_Items(self, parent): parent.AddWindow(self._ribbon, 0, wx.EXPAND) parent.AddWindow(self.pnlDocking, 85, flag=wx.ALL | wx.EXPAND) - def _init_database(self, quit_if_cancel=True): + def _init_database(self, quit_if_cancel=True, newConnection= ''): logger.debug("Loading Database...") self.service_manager = ServiceManager() @@ -122,7 +122,10 @@ def _init_database(self, quit_if_cancel=True): while True: ## Database connection is valid, therefore proceed through the rest of the program - conn_dict = self.service_manager.is_valid_connection() + if newConnection: + conn_dict= newConnection + else: + conn_dict = self.service_manager.is_valid_connection() if conn_dict: #conn_dict = None @@ -436,7 +439,7 @@ def onChangeDBConn(self, event): newConnection = db_config.panel.getFieldValues() db_config.Destroy() - if self._init_database(quit_if_cancel=False): + if self._init_database(quit_if_cancel=False, newConnection=newConnection): # if editing, stop editing... if self._ribbon.getEditStatus(): self.stopEdit(event=None) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index a4d447e..29d6252 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -63,6 +63,7 @@ # ValueTypeCV=ODM.ValueTypeCV Variable = ODM.Variable VerticalDatumCV=ODM.VerticalDatumCV +returnDict = ODM.returnDict from odmtools.odmdata.memory_database import MemoryDatabase @@ -97,6 +98,7 @@ 'VerticalDatumCV', 'refreshDB', 'change_schema', + 'returnDict', 'MemoryDatabase', diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 3e19a47..a7e8447 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -41,7 +41,7 @@ def __init__(self, debug=False): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] - line_dict['version']=1.1 + line_dict['version']='1.1' if len(line) == 5 else line[5] self._conn_dicts.append(line_dict) if len(self._conn_dicts) is not 0: @@ -55,39 +55,12 @@ def __init__(self, debug=False): def get_all_conn_dicts(self): return self._conn_dicts - # def is_valid_connection(self): - # if self._current_conn_dict: - # conn_string = self._build_connection_string(self._current_conn_dict) - # logger.debug("Conn_string: %s" % conn_string) - # try: - # if self.testEngine(conn_string): - # return self._current_conn_dict - # - # return None - - def is_valid_connection(self): - if self._current_conn_dict: - conn_string = self._build_connection_string(self._current_conn_dict) - logger.debug("Conn_string: %s" % conn_string) - dbtype = self._current_conn_dict['version'] - #dbtype =1.1 - refreshDB(dbtype) - - try: - if self.testEngine(conn_string): - return self._current_conn_dict - except Exception as e: - logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) - return None - - - def get_current_conn_dict(self): return self._current_conn_dict - # def set_current_conn_dict(self, dict): - # self._current_conn_dict = dict + def set_current_conn_dict(self, dict): + self._current_conn_dict = dict def add_connection(self, conn_dict): """conn_dict must be a dictionary with keys: engine, user, password, address, db""" @@ -139,10 +112,37 @@ def testEngine(self, connection_string): print "Connection was unsuccessful ", e.message return False return True + # def is_valid_connection(self): + # if self._current_conn_dict: + # conn_string = self._build_connection_string(self._current_conn_dict) + # logger.debug("Conn_string: %s" % conn_string) + # try: + # if self.testEngine(conn_string): + # return self._current_conn_dict + # + # return None + + def is_valid_connection(self): + if self._current_conn_dict: + conn_string = self._build_connection_string(self._current_conn_dict) + logger.debug("Conn_string: %s" % conn_string) + dbtype = float(self._current_conn_dict['version']) + #dbtype =1.1 + refreshDB(dbtype) + + try: + if self.testEngine(conn_string): + return self._current_conn_dict + except Exception as e: + logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) + return None def test_connection(self, conn_dict): try: conn_string = self._build_connection_string(conn_dict) + dbtype = float(conn_dict['version']) + #dbtype =1.1 + refreshDB(dbtype) if self.testEngine(conn_string):# and self.get_db_version(conn_string) == '1.1.1': return True except SQLAlchemyError as e: @@ -248,6 +248,6 @@ def _build_connection_string(self, conn_dict): def _save_connections(self): f = self._get_file('w') for conn in self._conn_dicts: - f.write("%s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'])) + f.write("%s %s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'], conn['version'])) f.close() diff --git a/odmtools/view/clsDBConfig.py b/odmtools/view/clsDBConfig.py index 4d2490e..8553044 100644 --- a/odmtools/view/clsDBConfig.py +++ b/odmtools/view/clsDBConfig.py @@ -1,4 +1,140 @@ -# -*- coding: utf-8 -*- +# # -*- coding: utf-8 -*- +# +# # ########################################################################## +# ## Python code generated with wxFormBuilder (version Jun 5 2014) +# ## http://www.wxformbuilder.org/ +# ## +# ## PLEASE DO "NOT" EDIT THIS FILE! +# ########################################################################### +# +# import wx +# import wx.xrc +# +# ########################################################################### +# ## Class clsDBConfiguration +# ########################################################################### +# +# class clsDBConfiguration(wx.Panel): +# def __init__(self, parent): +# +# wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 291), style=wx.SIMPLE_BORDER | wx.TAB_TRAVERSAL) +# +# self.SetMinSize(wx.Size(442, 291)) +# self.SetMaxSize(wx.Size(627, 291)) +# +# formSizer = wx.BoxSizer(wx.VERTICAL) +# +# sbSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"Database Connection"), wx.VERTICAL) +# +# connectionSizer = wx.FlexGridSizer(0, 2, 0, 15) +# connectionSizer.AddGrowableCol(1) +# connectionSizer.SetFlexibleDirection(wx.VERTICAL) +# connectionSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) +# +# self.stConnType = wx.StaticText(self, wx.ID_ANY, u"Connection Type:", wx.DefaultPosition, wx.DefaultSize, +# wx.ALIGN_RIGHT) +# self.stConnType.Wrap(-1) +# connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND, 5) +# +# cbDatabaseTypeChoices = [] +# self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, +# cbDatabaseTypeChoices, wx.CB_READONLY) +# connectionSizer.Add(self.cbDatabaseType, 1, wx.ALL | wx.EXPAND, 5) +# +# self.stServer = wx.StaticText(self, wx.ID_ANY, u"Server:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stServer.Wrap(-1) +# connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtServer = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.FULL_REPAINT_ON_RESIZE | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtServer, 1, wx.ALL | wx.EXPAND, 5) +# +# self.stDBName = wx.StaticText(self, wx.ID_ANY, u"Database:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stDBName.Wrap(-1) +# self.stDBName.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtDBName = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtDBName, 0, wx.ALL | wx.EXPAND, 5) +# +# self.stUser = wx.StaticText(self, wx.ID_ANY, u"User:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stUser.Wrap(-1) +# self.stUser.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtUser = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtUser, 0, wx.ALL | wx.EXPAND, 5) +# +# self.stPass = wx.StaticText(self, wx.ID_ANY, u"Password:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stPass.Wrap(-1) +# self.stPass.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtPass = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# wx.TE_PASSWORD | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtPass, 0, wx.ALL | wx.EXPAND, 5) +# +# sbSizer.Add(connectionSizer, 1, wx.EXPAND, 5) +# +# formSizer.Add(sbSizer, 1, wx.EXPAND, 20) +# +# btnSizer = wx.FlexGridSizer(0, 3, 0, 25) +# btnSizer.AddGrowableCol(0) +# btnSizer.AddGrowableCol(1) +# btnSizer.AddGrowableCol(2) +# btnSizer.SetFlexibleDirection(wx.VERTICAL) +# btnSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) +# +# self.btnTest = wx.Button(self, wx.ID_ANY, u"Test Connection", wx.DefaultPosition, wx.DefaultSize, 0) +# btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND, 5) +# +# self.btnSave = wx.Button(self, wx.ID_ANY, u"Save Connection", wx.DefaultPosition, wx.DefaultSize, 0) +# self.btnSave.Enable(False) +# +# btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND, 5) +# +# self.btnCancel = wx.Button(self, wx.ID_ANY, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0) +# btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND, 5) +# +# formSizer.Add(btnSizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED, 15) +# +# self.SetSizer(formSizer) +# self.Layout() +# +# # Connect Events +# self.cbDatabaseType.Bind(wx.EVT_COMBOBOX, self.OnValueChanged) +# self.txtServer.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtDBName.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtUser.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtPass.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.btnTest.Bind(wx.EVT_BUTTON, self.OnBtnTest) +# self.btnSave.Bind(wx.EVT_BUTTON, self.OnBtnSave) +# self.btnCancel.Bind(wx.EVT_BUTTON, self.OnBtnCancel) +# +# def __del__(self): +# pass +# +# +# # Virtual event handlers, overide them in your derived class +# def OnValueChanged(self, event): +# event.Skip() +# +# +# def OnBtnTest(self, event): +# event.Skip() +# +# def OnBtnSave(self, event): +# event.Skip() +# +# def OnBtnCancel(self, event): +# event.Skip() +# +# -*- coding: utf-8 -*- # ########################################################################## ## Python code generated with wxFormBuilder (version Jun 5 2014) @@ -16,7 +152,6 @@ class clsDBConfiguration(wx.Panel): def __init__(self, parent): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 291), style=wx.SIMPLE_BORDER | wx.TAB_TRAVERSAL) @@ -32,19 +167,30 @@ def __init__(self, parent): connectionSizer.SetFlexibleDirection(wx.VERTICAL) connectionSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) + self.stVersion = wx.StaticText(self, wx.ID_ANY, u"DB Version:", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.stVersion.Wrap(-1) + connectionSizer.Add(self.stVersion, 0, wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, 5) + + version_choices = [ u"1.1", u"2.0"] + self.cbVersion = wx.ComboBox(self, wx.ID_ANY, u"1.1", wx.DefaultPosition, wx.DefaultSize, + version_choices, wx.CB_READONLY | wx.CB_SORT) + self.cbVersion.SetSelection(0) + connectionSizer.Add(self.cbVersion, 1, wx.ALL | wx.EXPAND, 5) + self.stConnType = wx.StaticText(self, wx.ID_ANY, u"Connection Type:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stConnType.Wrap(-1) - connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) cbDatabaseTypeChoices = [] - self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, - cbDatabaseTypeChoices, wx.CB_READONLY) + self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, + cbDatabaseTypeChoices, wx.CB_READONLY | wx.CB_SORT) connectionSizer.Add(self.cbDatabaseType, 1, wx.ALL | wx.EXPAND, 5) self.stServer = wx.StaticText(self, wx.ID_ANY, u"Server:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stServer.Wrap(-1) - connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtServer = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.FULL_REPAINT_ON_RESIZE | wx.SIMPLE_BORDER) @@ -54,35 +200,35 @@ def __init__(self, parent): self.stDBName.Wrap(-1) self.stDBName.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtDBName = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtDBName, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtDBName, 1, wx.ALL | wx.EXPAND, 5) self.stUser = wx.StaticText(self, wx.ID_ANY, u"User:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stUser.Wrap(-1) self.stUser.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtUser = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtUser, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtUser, 1, wx.ALL | wx.EXPAND, 5) self.stPass = wx.StaticText(self, wx.ID_ANY, u"Password:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stPass.Wrap(-1) self.stPass.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtPass = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtPass, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtPass, 1, wx.ALL | wx.EXPAND, 5) - sbSizer.Add(connectionSizer, 1, wx.EXPAND, 5) + sbSizer.Add(connectionSizer, 90, wx.EXPAND, 3) - formSizer.Add(sbSizer, 1, wx.EXPAND, 20) + formSizer.Add(sbSizer, 1, wx.ALL | wx.EXPAND, 7) btnSizer = wx.FlexGridSizer(0, 3, 0, 25) btnSizer.AddGrowableCol(0) @@ -92,17 +238,15 @@ def __init__(self, parent): btnSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) self.btnTest = wx.Button(self, wx.ID_ANY, u"Test Connection", wx.DefaultPosition, wx.DefaultSize, 0) - btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) self.btnSave = wx.Button(self, wx.ID_ANY, u"Save Connection", wx.DefaultPosition, wx.DefaultSize, 0) - self.btnSave.Enable(False) - - btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) self.btnCancel = wx.Button(self, wx.ID_ANY, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0) - btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) - formSizer.Add(btnSizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED, 15) + formSizer.Add(btnSizer, 10, wx.EXPAND, 2) self.SetSizer(formSizer) self.Layout() @@ -120,12 +264,10 @@ def __init__(self, parent): def __del__(self): pass - # Virtual event handlers, overide them in your derived class def OnValueChanged(self, event): event.Skip() - def OnBtnTest(self, event): event.Skip() @@ -135,3 +277,4 @@ def OnBtnSave(self, event): def OnBtnCancel(self, event): event.Skip() + From 9c6e794058c55b8dfca5b92765288d5d793b71fc Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Thu, 10 Sep 2015 14:51:11 -0600 Subject: [PATCH 007/158] fix errors with ODM2 connection --- odmtools/controller/frmDBConfig.py | 4 +-- odmtools/controller/olvSeriesSelector.py | 4 +-- odmtools/odmdata/__init__.py | 11 ++++-- odmtools/odmdata/memory_database.py | 14 ++++---- odmtools/odmservices/service_manager.py | 34 +++++++++---------- tests/test_odmservices/test_series_service.py | 8 ++--- 6 files changed, 39 insertions(+), 36 deletions(-) diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py index 46ee811..19068f8 100755 --- a/odmtools/controller/frmDBConfig.py +++ b/odmtools/controller/frmDBConfig.py @@ -94,8 +94,8 @@ def validateInput(self, conn_dict): #TODO add error message if user cannont connect to the database ( not using VPN) but the db is still 1.1.1) if not (self.service_manager.get_db_version(conn_dict)): message = "Cannot connect to the database" - else: - message = "This connection is not a 1.1.1 Database" + # else: + # message = "This connection is not a 1.1.1 Database" wx.MessageBox(message, 'Error Occurred', wx.OK | wx.ICON_ERROR) return False diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 0a9a4f1..8002d8d 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -6,7 +6,7 @@ from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn from odmtools.common.logger import LoggerTool -from odmtools.odmdata import Series +from odmtools.odmdata import ODM tool = LoggerTool() @@ -48,7 +48,7 @@ def _buildColumns(self): ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in Series.returnDict().iteritems()] + for key, value in ODM.returnDict().iteritems()] self.SetColumns(seriesColumns) """User can select series using the mouse to click on check boxes """ diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 27362a8..aaf559e 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -38,7 +38,9 @@ from api.ODMconnection import SessionFactory -from api.versionSwitcher import ODM +from api.versionSwitcher import ODM, refreshDB +from api.ODM2.models import change_schema + DataTypeCV=ODM.DataTypeCV DataValue=ODM.DataValue # GeneralCategoryCV=ODM.GeneralCategoryCV @@ -62,6 +64,7 @@ # ValueTypeCV=ODM.ValueTypeCV Variable = ODM.Variable VerticalDatumCV=ODM.VerticalDatumCV +returnDict = ODM.returnDict from odmtools.odmdata.memory_database import MemoryDatabase @@ -74,7 +77,7 @@ 'ISOMetadata', 'LabMethod', 'Method', - 'ODMVersion', + #'ODMVersion', 'OffsetType', 'Qualifier', 'QualityControlLevel', @@ -94,7 +97,9 @@ 'Variable', 'VariableNameCV', 'VerticalDatumCV', + 'refreshDB', + 'change_schema', + 'returnDict', 'MemoryDatabase', - ] diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 8c0a526..194a632 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -85,10 +85,10 @@ def getEditDataValuesforGraph(self): return self.mem_service.get_all_plot_values() def commit(self): - self.mem_service._edit_session.commit() + self.mem_service._session.commit() def rollback(self): - self.mem_service._edit_session.rollback() + self.mem_service._session.rollback() # self.mem_service._session_factory.engine.connect().connection.rollback() #self.updateDF() @@ -102,7 +102,7 @@ def update(self, updates): values(DataValue=bindparam('value')) ) - self.mem_service._edit_session.execute(stmt, updates) + self.mem_service._session.execute(stmt, updates) # self.updateDF() @@ -122,7 +122,7 @@ def updateValue(self, ids, operator, value): #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) for c in chunks: - q=self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c)) + q=self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c)) q.update({DataValue.data_value: query}, False) #self.updateDF() @@ -134,7 +134,7 @@ def chunking(self, data): def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: - self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ + self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ .update({DataValue.qualifier_id: value}, False) @@ -163,7 +163,7 @@ def addPoints(self, points): "SiteID": point[10], "VariableID": point[11], "MethodID": point[12], "SourceID": point[13], "QualityControlLevelID": point[14]} - self.mem_service._edit_session.execute(stmt, vals) + self.mem_service._session.execute(stmt, vals) def stopEdit(self): @@ -222,7 +222,7 @@ def changeSeriesIDs(self, var=None, qcl=None, method=None): :return: """ - query = self.mem_service._edit_session.query(DataValue) + query = self.mem_service._session.query(DataValue) if var is not None: logger.debug(var) query.update({DataValue.variable_id: var}) diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 6348981..e00c2ff 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -87,11 +87,9 @@ def add_connection(self, conn_dict): logger.error("Unable to save connection due to invalid connection to database") return False - @classmethod def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) - s.test_Session().query(Variable.code).limit(1).first() return True @@ -99,7 +97,7 @@ def testEngine(self, connection_string): def test_connection(self, conn_dict): try: conn_string = self._build_connection_string(conn_dict) - if self.testEngine(conn_string) and self.get_db_version(conn_string) == '1.1.1': + if self.testEngine(conn_string): #and self.get_db_version(conn_string) == '1.1.1': return True except SQLAlchemyError as e: logger.error("SQLAlchemy Error: %s" % e.message) @@ -113,21 +111,21 @@ def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] # Create and return services based on the currently active connection - def get_db_version_dict(self, conn_dict): - conn_string = self._build_connection_string(conn_dict) - self.get_db_version(conn_string) - - def get_db_version(self, conn_string): - if isinstance(conn_string, dict): - conn_string = self._build_connection_string(conn_string) - service = SeriesService(conn_string) - #if not self.version: - try: - self.version = service.get_db_version() - except Exception as e: - logger.error("Exception: %s" % e.message) - return None - return self.version + # def get_db_version_dict(self, conn_dict): + # conn_string = self._build_connection_string(conn_dict) + # self.get_db_version(conn_string) + + # def get_db_version(self, conn_string): + # if isinstance(conn_string, dict): + # conn_string = self._build_connection_string(conn_string) + # service = SeriesService(conn_string) + # #if not self.version: + # try: + # self.version = service.get_db_version() + # except Exception as e: + # logger.error("Exception: %s" % e.message) + # return None + # return self.version def get_series_service(self, conn_dict="", conn_string = ""): diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index d33f5a5..5f708c3 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -12,10 +12,10 @@ def setup(self): engine = self.series_service._session_factory.engine test_util.build_db(engine) - def test_get_db_version(self): - version = test_util.add_version(self.session) - db_version = self.series_service.get_db_version() - assert version.version_number == db_version + # def test_get_db_version(self): + # version = test_util.add_version(self.session) + # db_version = self.series_service.get_db_version() + # assert version.version_number == db_version def test_get_all_sites_empty(self): sites = self.series_service.get_used_sites() From 0504fe2ef542c94bec41588bfff8d57589716dad Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 22 Sep 2015 11:51:45 -0600 Subject: [PATCH 008/158] update code to work with new ODM format --- odmtools/controller/frmCreateVariable.py | 36 +++--- odmtools/controller/olvSeriesSelector.py | 23 +++- odmtools/gui/frmFlagValues.py | 2 +- odmtools/gui/pageMethod.py | 9 +- odmtools/gui/pageQCL.py | 5 +- odmtools/gui/pageVariable.py | 4 +- odmtools/odmdata/__init__.py | 135 ++++++++--------------- odmtools/odmdata/memory_database.py | 30 ++--- odmtools/odmservices/__init__.py | 4 +- odmtools/odmservices/service_manager.py | 12 +- 10 files changed, 119 insertions(+), 141 deletions(-) diff --git a/odmtools/controller/frmCreateVariable.py b/odmtools/controller/frmCreateVariable.py index 5b49153..056cce6 100644 --- a/odmtools/controller/frmCreateVariable.py +++ b/odmtools/controller/frmCreateVariable.py @@ -2,7 +2,7 @@ import wx from odmtools.view.clsCreateVariable import clsCreateVariable -from odmtools.odmdata import Variable +# from odmtools.odmdata import Variable # Implementing clsCreateVariable class frmCreateVariable(clsCreateVariable): @@ -87,26 +87,30 @@ def OnBtnCreateButton(self, event): def createVariable(self): - v = Variable() - v.code = self.txtVarCode.GetValue() if self.txtVarCode.GetValue() <> u'' else None - v.name = self.cbVarName.GetValue() if self.cbVarName.GetValue() <> u'' else None - v.speciation = self.cbSpeciation.GetValue() if self.cbSpeciation.GetValue() <> u'' else None - v.variable_unit = self.series_service.get_unit_by_name( self.cbVarUnits.GetValue()) - v.variable_unit_id = v.variable_unit.id + code = self.txtVarCode.GetValue() if self.txtVarCode.GetValue() <> u'' else None + name = self.cbVarName.GetValue() if self.cbVarName.GetValue() <> u'' else None + speciation = self.cbSpeciation.GetValue() if self.cbSpeciation.GetValue() <> u'' else None - v.sample_medium = self.cbSampleMedium.GetValue() if self.cbSampleMedium.GetValue() <> u'' else None - v.value_type = self.cbValueType.GetValue() if self.cbValueType.GetValue() <> u'' else None - v.is_regular = self.cbIsRegular.GetValue() if self.cbIsRegular.GetValue() <> u'' else None - v.time_support = self.txtTSValue.GetValue() if self.txtTSValue.GetValue() <> u'' else None + variable_unit = self.series_service.get_unit_by_name( self.cbVarUnits.GetValue()) + variable_unit_id = variable_unit.id - v.time_unit = self.series_service.get_unit_by_name(self.cbTSUnits.GetValue()) - v.time_unit_id = v.time_unit.id + sample_medium = self.cbSampleMedium.GetValue() if self.cbSampleMedium.GetValue() <> u'' else None + value_type = self.cbValueType.GetValue() if self.cbValueType.GetValue() <> u'' else None + is_regular = self.cbIsRegular.GetValue() if self.cbIsRegular.GetValue() <> u'' else None + time_support = self.txtTSValue.GetValue() if self.txtTSValue.GetValue() <> u'' else None - v.data_type = self.cbDataType.GetValue() if self.cbDataType.GetValue() <> u'' else None - v.general_category = self.cbGenCat.GetValue() if self.cbGenCat.GetValue() <> u'' else None - v.no_data_value = self.txtNoDV.GetValue() if self.txtNoDV.GetValue() <> u'' else None + time_unit = self.series_service.get_unit_by_name(self.cbTSUnits.GetValue()) + time_unit_id = time_unit.id + + data_type = self.cbDataType.GetValue() if self.cbDataType.GetValue() <> u'' else None + general_category = self.cbGenCat.GetValue() if self.cbGenCat.GetValue() <> u'' else None + no_data_value = self.txtNoDV.GetValue() if self.txtNoDV.GetValue() <> u'' else None + v = self.series_service.create_variable( + code, name, speciation, variable_unit_id, sample_medium, + value_type, is_regular, time_support, time_unit_id, data_type, + general_category, no_data_value) return v def OnBtnCancelButton(self, event): diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 8002d8d..daf92b8 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -4,16 +4,29 @@ import wx.lib.newevent # from ObjectListView.ObjectListView import FastObjectListView, ColumnDefn from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn - +# from odmtools.odmdata import returnDict from odmtools.common.logger import LoggerTool -from odmtools.odmdata import ODM - tool = LoggerTool() logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() - +from collections import OrderedDict +def returnDict(): + keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', + 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', + 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', + 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', + 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' + ] + values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', + 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', + 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', + 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', + 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', + 'end_date_time_utc', 'value_count' + ] + return OrderedDict(zip(keys, values)) class clsSeriesTable(FastObjectListView): def __init__(self, *args, **kwargs): @@ -48,7 +61,7 @@ def _buildColumns(self): ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in ODM.returnDict().iteritems()] + for key, value in returnDict().iteritems()] self.SetColumns(seriesColumns) """User can select series using the mouse to click on check boxes """ diff --git a/odmtools/gui/frmFlagValues.py b/odmtools/gui/frmFlagValues.py index f149a33..e7035f1 100755 --- a/odmtools/gui/frmFlagValues.py +++ b/odmtools/gui/frmFlagValues.py @@ -2,7 +2,7 @@ import wx -from odmtools.odmdata import Qualifier + def create(parent): diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py index f422d4a..210770e 100644 --- a/odmtools/gui/pageMethod.py +++ b/odmtools/gui/pageMethod.py @@ -3,7 +3,7 @@ import wx import wx.grid import wx.richtext -from odmtools.odmdata import Method + [wxID_PNLMETHOD, wxID_PNLMETHODSLISTCTRL1, wxID_PNLMETHODSRBCREATENEW, wxID_PNLMETHODSRBGENERATE, wxID_PNLMETHODSRBSELECT, @@ -105,14 +105,15 @@ def OnTxtMethodDescripKillFocus(self, event): def getMethod(self): - m = Method() + m = None if self.rbGenerate.Value: genmethod = "Values derived from ODM Tools Python" try: m= self.series_service.get_method_by_description(genmethod) except: - m.description = genmethod + m =self.series_service(genmethod) + elif self.rbSelect.Value: index = self.lstMethods.GetFirstSelected() @@ -124,5 +125,5 @@ def getMethod(self): elif self.rbCreateNew.Value: - m.description = self.txtMethodDescrip.GetValue() + m =self.series_service( self.txtMethodDescrip.GetValue()) return m \ No newline at end of file diff --git a/odmtools/gui/pageQCL.py b/odmtools/gui/pageQCL.py index af0e411..4c285dc 100644 --- a/odmtools/gui/pageQCL.py +++ b/odmtools/gui/pageQCL.py @@ -1,7 +1,7 @@ #Boa:FramePanel:pnlQCL import wx -from odmtools.odmdata import QualityControlLevel + [wxID_PNLQCL, wxID_PNLQCLLBLCODE, wxID_PNLQCLLBLDEFINITION, wxID_PNLQCLLBLEXPLANATION, wxID_PNLQCLLSTQCL, wxID_PNLQCLRBCREATE, @@ -110,8 +110,9 @@ def GetLstSelection(self): return self.lstQCL.GetFirstSelected() def getQCL(self): - q = QualityControlLevel() + q = None if self.rbCreate.Value: + q= self.series_service.create_qcl(self.txtCode.Value, self.txtDefinition.Value, self.txtExplanation.Value) q.code = self.txtCode.Value q.definition= self.txtDefinition.Value q.explanation = self.txtExplanation.Value diff --git a/odmtools/gui/pageVariable.py b/odmtools/gui/pageVariable.py index 95a7260..e479bbb 100644 --- a/odmtools/gui/pageVariable.py +++ b/odmtools/gui/pageVariable.py @@ -2,7 +2,7 @@ import wx from odmtools.controller.frmCreateVariable import frmCreateVariable -from odmtools.odmdata import Variable +# from odmtools.odmdata import Variable [wxID_PNLVARIABLE, wxID_PNLVARIABLELSTVARIABLE, wxID_PNLVARIABLERBCREATE, wxID_PNLVARIABLERBCURRENT, wxID_PNLVARIABLERBSELECT,wxID_PNLVARIABLETXTNEWVAR, @@ -138,7 +138,7 @@ def OnListCtrl1ListItemSelected(self, event): def getVariable(self): - v = Variable() + v = None if self.rbCurrent.Value: v= self.prev_val elif self.rbSelect.Value: diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index aaf559e..31d31aa 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,105 +1,64 @@ -''' -__all__ = [ - 'Base', - 'CensorCodeCV', - 'DataTypeCV', - 'DataValue', - 'GeneralCategoryCV', - 'ISOMetadata', - 'LabMethod', - 'Method', - 'ODMVersion', - 'OffsetType', - 'Qualifier', - 'QualityControlLevel', - 'Sample', - 'SampleMediumCV', - 'SampleTypeCV', - 'Series', - 'SessionFactory', - 'Site', - 'SiteTypeCV', - 'Source', - 'SpatialReference', - 'SpeciationCV', - 'TopicCategoryCV', - 'Unit', - 'ValueTypeCV', - 'Variable', - 'VariableNameCV', - 'VerticalDatumCV', - 'MemoryDatabase', -] -''' -from api.ODMconnection import SessionFactory -from api.versionSwitcher import ODM, refreshDB -from api.ODM2.models import change_schema +#from api.versionSwitcher import ODM, refreshDB -DataTypeCV=ODM.DataTypeCV -DataValue=ODM.DataValue -# GeneralCategoryCV=ODM.GeneralCategoryCV -ISOMetadata=ODM.ISOMetadata -LabMethod=ODM.LabMethod -Method=ODM.Method -OffsetType=ODM.OffsetType -Qualifier=ODM.Qualifier -QualityControlLevel=ODM.QualityControlLevel -Sample =ODM.Sample -SampledMediumCV= ODM.SampleMediumCV -# SampleTypeCV=ODM.SampleTypeCV -Series=ODM.Series -Site= ODM.Site -SiteType=ODM.SiteTypeCV -Source =ODM.Source -SpatialReferences=ODM.SpatialReference -SpeciationCV=ODM.SpeciationCV -# TopicCategoryCV=ODM.TopicCategoryCV -Unit= ODM.Unit -# ValueTypeCV=ODM.ValueTypeCV -Variable = ODM.Variable -VerticalDatumCV=ODM.VerticalDatumCV -returnDict = ODM.returnDict +# DataTypeCV=ODM.DataTypeCV +# DataValue=ODM.DataValue +# # GeneralCategoryCV=ODM.GeneralCategoryCV +# ISOMetadata=ODM.ISOMetadata +# LabMethod=ODM.LabMethod +# Method=ODM.Method +# OffsetType=ODM.OffsetType +# Qualifier=ODM.Qualifier +# QualityControlLevel=ODM.QualityControlLevel +# Sample =ODM.Sample +# SampledMediumCV= ODM.SampleMediumCV +# # SampleTypeCV=ODM.SampleTypeCV +# Series=ODM.Series +# Site= ODM.Site +# SiteType=ODM.SiteTypeCV +# Source =ODM.Source +# SpatialReferences=ODM.SpatialReference +# SpeciationCV=ODM.SpeciationCV +# # TopicCategoryCV=ODM.TopicCategoryCV +# Unit= ODM.Unit +# # ValueTypeCV=ODM.ValueTypeCV +# Variable = ODM.Variable +# VerticalDatumCV=ODM.VerticalDatumCV +# returnDict = ODM.returnDict +from api.ODM1_1_1.services.series_service import ODM, refreshDB +from api.ODMconnection import SessionFactory +from api.ODM2.models import change_schema from odmtools.odmdata.memory_database import MemoryDatabase + +from collections import OrderedDict +def returnDict(): + keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', + 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', + 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', + 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', + 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' + ] + values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', + 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', + 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', + 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', + 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', + 'end_date_time_utc', 'value_count' + ] + return OrderedDict(zip(keys, values)) __all__=[ 'SessionFactory', - 'CensorCodeCV', - 'DataTypeCV', - 'DataValue', - 'GeneralCategoryCV', - 'ISOMetadata', - 'LabMethod', - 'Method', - #'ODMVersion', - 'OffsetType', - 'Qualifier', - 'QualityControlLevel', - 'Sample', - 'SampleMediumCV', - 'SampleTypeCV', - 'Series', - 'SessionFactory', - 'Site', - 'SiteTypeCV', - 'Source', - 'SpatialReference', - 'SpeciationCV', - 'TopicCategoryCV', - 'Unit', - 'ValueTypeCV', - 'Variable', - 'VariableNameCV', - 'VerticalDatumCV', 'refreshDB', 'change_schema', 'returnDict', - + 'ODM', 'MemoryDatabase', + 'returnDict' ] diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 194a632..ceefb98 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -5,7 +5,7 @@ from odmtools.common.logger import LoggerTool from odmtools.odmservices import SeriesService from odmtools.odmservices import ServiceManager -from odmtools.odmdata import DataValue +from odmtools.odmdata import ODM tool = LoggerTool() logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) @@ -97,8 +97,8 @@ def update(self, updates): updates : list of dictionary that contains 2 items, id and value ''' - stmt = (DataValue.__table__.update(). - where(DataValue.local_date_time == bindparam('id')). + stmt = (ODM.DataValue.__table__.update(). + where(ODM.DataValue.local_date_time == bindparam('id')). values(DataValue=bindparam('value')) ) @@ -110,11 +110,11 @@ def update(self, updates): def updateValue(self, ids, operator, value): # query = DataValue.data_value+value if operator == '+': - query = DataValue.data_value + value + query = ODM.DataValue.data_value + value elif operator == '-': - query = DataValue.data_value - value + query = ODM.DataValue.data_value - value elif operator == '*': - query = DataValue.data_value * value + query = ODM.DataValue.data_value * value elif operator == '=': query = value @@ -122,8 +122,8 @@ def updateValue(self, ids, operator, value): #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) for c in chunks: - q=self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c)) - q.update({DataValue.data_value: query}, False) + q=self.mem_service._session.query(ODM.DataValue).filter(ODM.DataValue.local_date_time.in_(c)) + q.update({ODM.DataValue.data_value: query}, False) #self.updateDF() @@ -134,8 +134,8 @@ def chunking(self, data): def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: - self.mem_service._session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ - .update({DataValue.qualifier_id: value}, False) + self.mem_service._session.query(ODM.DataValue).filter(ODM.DataValue.local_date_time.in_(c))\ + .update({ODM.DataValue.qualifier_id: value}, False) def delete(self, ids): @@ -149,7 +149,7 @@ def addPoints(self, points): """ Takes in a list of points and loads each point into the database """ - stmt = DataValue.__table__.insert() + stmt = ODM.DataValue.__table__.insert() if not isinstance(points, list): points = [points] @@ -222,17 +222,17 @@ def changeSeriesIDs(self, var=None, qcl=None, method=None): :return: """ - query = self.mem_service._session.query(DataValue) + query = self.mem_service._session.query(ODM.DataValue) if var is not None: logger.debug(var) - query.update({DataValue.variable_id: var}) + query.update({ODM.DataValue.variable_id: var}) if method is not None: logger.debug(method) - query.update({DataValue.method_id: method}) + query.update({ODM.DataValue.method_id: method}) # check that the code is not zero # if qcl is not None and qcl.code != 0: if qcl is not None: logger.debug(qcl) - query.update({DataValue.quality_control_level_id: qcl}) + query.update({ODM.DataValue.quality_control_level_id: qcl}) diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 23dd7c6..1d39644 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -9,12 +9,12 @@ # import pyodbc # #import psycopg2 -from api.ODM1_1_1.services import SeriesService, EditService, CVService, ExportService +from api.ODM1_1_1.services import SeriesService, EditService, ExportService#, , CVService, from service_manager import ServiceManager __all__ = [ 'EditService', - 'CVService', + #'CVService', 'SeriesService', 'ExportService', 'ServiceManager', diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 19c42ba..d287535 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -5,10 +5,10 @@ import urllib from sqlalchemy.exc import SQLAlchemyError -from odmtools.odmservices import SeriesService, CVService, EditService, ExportService +from odmtools.odmservices import SeriesService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SessionFactory, Variable, refreshDB, change_schema +from odmtools.odmdata import SessionFactory, change_schema, refreshDB, ODM from odmtools.common.logger import LoggerTool @@ -105,7 +105,7 @@ def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) try: # s.ms_test_Session().query(Variable1).limit(1).first() - s.test_Session().query(Variable.code).limit(1).first() + s.test_Session().query(ODM.Variable.code).limit(1).first() except Exception as e: @@ -186,9 +186,9 @@ def get_series_service(self, conn_dict="", conn_string = ""): ss= SeriesService(sf) return ss - def get_cv_service(self): - conn_string = self._build_connection_string(self._current_conn_dict) - return CVService(SessionFactory(conn_string, self.debug)) + # def get_cv_service(self): + # conn_string = self._build_connection_string(self._current_conn_dict) + # return CVService(SessionFactory(conn_string, self.debug)) def get_edit_service(self, series_id, connection): From 9624eb14875bc389e158220fbf8d422dc8df84cb Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Tue, 29 Sep 2015 09:59:15 -0600 Subject: [PATCH 009/158] update dbversion change --- odmtools/controller/logicPlotOptions.py | 20 ++++++-------------- odmtools/odmdata/__init__.py | 2 +- odmtools/odmdata/memory_database.py | 11 ++++++++--- odmtools/odmservices/service_manager.py | 24 ++++++++++++++---------- odmtools/view/clsDBConfig.py | 4 ++-- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index d1ae4e8..460e1a8 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -83,7 +83,6 @@ def __init__(self, memDB, taskserver): self.currentEnd = self.endDate self.isSubsetted = False - def getDates(self): return self.startDate, self.endDate, self.currentStart, self.currentEnd @@ -112,7 +111,6 @@ def resetDates(self): self.currentStart = self.startDate self.currentEnd = self.endDate - def isPlotted(self, sid): if int(sid) in self._seriesInfos: return True @@ -144,7 +142,6 @@ def setEditSeries(self, seriesID): self._seriesInfos[self.editID].plotcolor = self._seriesInfos[self.editID].color self._seriesInfos[self.editID].color = "Black" - def updateEditSeries(self): #update values if self.editID in self._seriesInfos: @@ -152,7 +149,6 @@ def updateEditSeries(self): data =self.memDB.getEditDataValuesforGraph() self._seriesInfos[self.editID].dataTable = data - def stopEditSeries(self): if self.editID in self._seriesInfos: data = self.memDB.getDataValuesforGraph( @@ -187,7 +183,6 @@ def update(self, key, isselected): #results = self.taskserver.getCompletedTasks() #self.memDB.setConnection(results["InitEditValues"]) - self._seriesInfos[key] = self.getSeriesInfo(key) self.getUpdatedData(key) @@ -244,6 +239,12 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): unitsName = series.variable_units_name siteName = series.site_name dataType = series.data_type + print type(series) + print dir(series) + print type(series.variable) + print dir(series.variable) + for x in series.variable: + print x noDataValue = series.variable.no_data_value if self.editID == seriesID: #d= DataFrame(pandas.read_sql()) @@ -251,13 +252,11 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): data = self.memDB.getEditDataValuesforGraph() logger.debug("Finished editing -- getting datavalues for graph") - else: logger.debug("plotting -- getting datavalues for graph") data = self.memDB.getDataValuesforGraph(seriesID, noDataValue, self.currentStart, self.currentEnd) logger.debug("Finished plotting -- getting datavalues for graph") - logger.debug("assigning variables...") seriesInfo.seriesID = seriesID seriesInfo.series = series @@ -273,13 +272,11 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): seriesInfo.noDataValue = noDataValue seriesInfo.dataTable = data - if len(data) > 0: seriesInfo.yrange = np.max(data['DataValue']) - np.min(data['DataValue']) else: seriesInfo.yrange = 0 - logger.debug("Finished creating SeriesInfo") return seriesInfo @@ -325,8 +322,6 @@ def buildPlotInfo(self, seriesInfo): seriesInfo.color = self.colorList.pop(0) return seriesInfo - - def updateDateRange(self, startDate=None, endDate=None): self.currentStart = startDate self.currentEnd = endDate @@ -375,7 +370,6 @@ def __init__(self, data): time = timeit.default_timer() - self.NumberofObservations = count self.ArithemticMean = round(np.mean(dvs), 5) self.Maximum = round(np.max(dvs), 5) @@ -403,7 +397,6 @@ def __init__(self, data, method): self.intervals = {} self.method = method - interval_types = ["Overall", "Year", "Month", "Season"] intervals = ["Overall", "Year", "Month", "Season"] @@ -450,7 +443,6 @@ def calculateBoxWhiskerData(self, interval, interval_type): interval_type, interval_type, results["names"], [results["median"], results["conflimit"], results["mean"], results["confint"]]) - def calculateIntervalsOnGroups(self, interval): mean = [] diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 31d31aa..79e14ac 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -31,7 +31,7 @@ # Variable = ODM.Variable # VerticalDatumCV=ODM.VerticalDatumCV # returnDict = ODM.returnDict -from api.ODM1_1_1.services.series_service import ODM, refreshDB +from api.ODM1_1_1.services.series_service import ODM#, refreshDB from api.ODMconnection import SessionFactory from api.ODM2.models import change_schema from odmtools.odmdata.memory_database import MemoryDatabase diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index ceefb98..242e0bf 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -24,6 +24,7 @@ def __init__(self, taskserver=None): # Memory_service handles in memory database sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + self.mem_service.refreshDB(1.1) # TODO clean up closing of program # if taskserver is None: #numproc = cpu_count() @@ -35,6 +36,7 @@ def __init__(self, taskserver=None): def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + self.mem_service.refreshDB(1.1) def set_series_service(self, service): self.series_service = service @@ -186,6 +188,8 @@ def updateDF(self): else: ''' self.df = self.mem_service.get_all_values_df() + print self.mem_service._version + def initEditValues(self, seriesID): """ @@ -206,12 +210,13 @@ def initEditValues(self, seriesID): # self.conn = results["InitEditValues"] else: ''' #TODO: Thread this call - if len(self.df)>0: + if self.df is not None and len(self.df)<=0: + logger.debug("no data in series") + else: + self.df.to_sql(name="DataValues", if_exists='replace', con=self.mem_service._session_factory.engine, index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") - else: - logger.debug("no data in series") def changeSeriesIDs(self, var=None, qcl=None, method=None): """ diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index d287535..28d7fa4 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,7 +8,7 @@ from odmtools.odmservices import SeriesService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SessionFactory, change_schema, refreshDB, ODM +from odmtools.odmdata import SessionFactory, change_schema, ODM#, refreshDB from odmtools.common.logger import LoggerTool @@ -21,7 +21,7 @@ def __init__(self, debug=False): self.debug = debug f = self._get_file('r') self._conn_dicts = [] - self.version = 0 + #self.version = 0 self._connection_format = "%s+%s://%s:%s@%s/%s" # Read all lines (connections) in the connection.cfg file @@ -41,9 +41,10 @@ def __init__(self, debug=False): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] - line_dict['version']='1.1' if len(line) == 5 else line[5] + line_dict['version']=1.1 if len(line) == 5 else line[5] self._conn_dicts.append(line_dict) + if len(self._conn_dicts) is not 0: # The current connection defaults to the most recent (i.e. the last written to the file) self._current_conn_dict = self._conn_dicts[-1] @@ -55,7 +56,6 @@ def __init__(self, debug=False): def get_all_conn_dicts(self): return self._conn_dicts - def get_current_conn_dict(self): return self._current_conn_dict @@ -79,7 +79,6 @@ def add_connection(self, conn_dict): # logger.error("Unable to save connection due to invalid connection to database") # return False - @staticmethod def _getSchema(engine): from sqlalchemy.engine import reflection @@ -128,7 +127,7 @@ def is_valid_connection(self): logger.debug("Conn_string: %s" % conn_string) dbtype = float(self._current_conn_dict['version']) #dbtype =1.1 - refreshDB(dbtype) + #refreshDB(dbtype) try: if self.testEngine(conn_string): @@ -143,7 +142,7 @@ def test_connection(self, conn_dict): dbtype = float(conn_dict['version']) #dbtype =1.1 - refreshDB(dbtype) + #refreshDB(dbtype) if self.testEngine(conn_string):# and self.get_db_version(conn_string) == '1.1.1': return True @@ -175,15 +174,20 @@ def delete_connection(self, conn_dict): # return None # return self.version - def get_series_service(self, conn_dict="", conn_string = ""): - + def get_series_service(self, conn_dict=None, conn_string=""): + version = 1.1 if conn_dict: conn_string = self._build_connection_string(conn_dict) #self._current_conn_dict = conn_dict + + version = float(conn_dict['version']) elif not conn_dict and not conn_string: conn_string = self._build_connection_string(self._current_conn_dict) - sf = SessionFactory(conn_string, self.debug) + version = float(self._current_conn_dict['version']) + + sf = SessionFactory(conn_string, self.debug, version = version) ss= SeriesService(sf) + ss.refreshDB(sf.version) return ss # def get_cv_service(self): diff --git a/odmtools/view/clsDBConfig.py b/odmtools/view/clsDBConfig.py index 8553044..3e6231b 100644 --- a/odmtools/view/clsDBConfig.py +++ b/odmtools/view/clsDBConfig.py @@ -174,7 +174,7 @@ def __init__(self, parent): version_choices = [ u"1.1", u"2.0"] self.cbVersion = wx.ComboBox(self, wx.ID_ANY, u"1.1", wx.DefaultPosition, wx.DefaultSize, - version_choices, wx.CB_READONLY | wx.CB_SORT) + version_choices, wx.CB_READONLY )#| wx.CB_SORT) self.cbVersion.SetSelection(0) connectionSizer.Add(self.cbVersion, 1, wx.ALL | wx.EXPAND, 5) @@ -185,7 +185,7 @@ def __init__(self, parent): cbDatabaseTypeChoices = [] self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbDatabaseTypeChoices, wx.CB_READONLY | wx.CB_SORT) + cbDatabaseTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) connectionSizer.Add(self.cbDatabaseType, 1, wx.ALL | wx.EXPAND, 5) self.stServer = wx.StaticText(self, wx.ID_ANY, u"Server:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) From ae54d9d2f46e801b949cd8d53dbf2313eec784eb Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 5 Oct 2015 12:31:23 -0600 Subject: [PATCH 010/158] update code to work with ODM2 api --- odmtools/controller/logicPlotOptions.py | 10 +++------- odmtools/gui/frmODMTools.py | 1 + odmtools/odmdata/memory_database.py | 1 + setup/make.py | 23 +++++++++++++---------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 460e1a8..82ef8b7 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -239,13 +239,9 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): unitsName = series.variable_units_name siteName = series.site_name dataType = series.data_type - print type(series) - print dir(series) - print type(series.variable) - print dir(series.variable) - for x in series.variable: - print x - noDataValue = series.variable.no_data_value + variable = self.memDB.series_service.get_variable_by_id(series.variable_id) + + noDataValue = variable.no_data_value if self.editID == seriesID: #d= DataFrame(pandas.read_sql()) logger.debug("editing -- getting datavalues for graph") diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index 4ac74be..cab5b27 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -463,6 +463,7 @@ def createService(self, conn_dict=""): """ series_service= self.service_manager.get_series_service(conn_dict=conn_dict)#=connection) + series_service.refreshDB(conn_dict['version']) return series_service def getServiceManager(self): diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 242e0bf..7a8e895 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -42,6 +42,7 @@ def set_series_service(self, service): self.series_service = service + ############## # DB Queries ############## diff --git a/setup/make.py b/setup/make.py index c1a2cfc..9d23d6f 100644 --- a/setup/make.py +++ b/setup/make.py @@ -160,16 +160,17 @@ def run_pyinstaller(console=False): '--noconfirm ' + APP_FILE) else: ## Non Console Version - val = os.system('pyinstaller ' - '--clean ' - '--distpath=%s ' % WIN_DIR + - '--workpath=%s ' % WORK_DIR + - '--specpath=%s ' % WIN_DIR + - '--upx-dir=%s ' % BASE_DIR + - '--icon=%s ' % WIN_ICON_FILE + - '--version-file=%s ' % VERSION_FILE + - '--noconsole ' - '--noconfirm ' + APP_FILE) + cmd = 'pyinstaller '+\ + '--clean '+\ + '--distpath=%s ' % WIN_DIR +\ + '--workpath=%s ' % WORK_DIR +\ + '--specpath=%s ' % WIN_DIR +\ + '--upx-dir=%s ' % BASE_DIR +\ + '--icon=%s ' % WIN_ICON_FILE +\ + '--version-file=%s ' % VERSION_FILE +\ + '--noconsole '+\ + '--noconfirm ' + APP_FILE + val = os.system(cmd) return True except Exception as e: @@ -237,10 +238,12 @@ def main(): print "Creating Windows Executable..." if run_pyinstaller(): + INNO_SCRIPT = os.path.join(WIN_DIR, "odmtools_no_console.iss") run_inno() print "Creating Windows Executable Console..." if run_pyinstaller(console=True): + INNO_SCRIPT = os.path.join(WIN_DIR, "odmtools_console.iss") run_inno() print "Create No Installer " From f99bf8cbec1e92b4203508d69ed1d866abb6294a Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Tue, 6 Oct 2015 13:44:07 -0600 Subject: [PATCH 011/158] update missing location to handle version switching --- odmtools/gui/frmODMTools.py | 62 ++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index cab5b27..2c2be32 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -31,6 +31,7 @@ tool = LoggerTool() logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) + class frmODMToolsMain(wx.Frame): """ @@ -50,7 +51,6 @@ def __init__(self, **kwargs): wx.Frame.__init__(self, **kwargs) - series_service = self._init_database() if series_service: self._init_ctrls(series_service) @@ -63,7 +63,6 @@ def __init__(self, **kwargs): logger.debug("System shutting down... ") sys.exit(0) - def _obtainScreenResolution(self): """ Calculates the size of ODMTools. Prevents ODMTools being larger than the available screen size typically a problem on laptops @@ -99,7 +98,6 @@ def _obtainScreenResolution(self): logger.debug("ODMTools Window Size: %s" % newSize) return newSize - #############Entire Form Sizers########## def _init_sizers(self): @@ -113,7 +111,7 @@ def _init_s_Items(self, parent): parent.AddWindow(self._ribbon, 0, wx.EXPAND) parent.AddWindow(self.pnlDocking, 85, flag=wx.ALL | wx.EXPAND) - def _init_database(self, quit_if_cancel=True, newConnection= ''): + def _init_database(self, quit_if_cancel=True, newConnection=''): logger.debug("Loading Database...") self.service_manager = ServiceManager() @@ -123,14 +121,14 @@ def _init_database(self, quit_if_cancel=True, newConnection= ''): while True: ## Database connection is valid, therefore proceed through the rest of the program if newConnection: - conn_dict= newConnection + conn_dict = newConnection else: conn_dict = self.service_manager.is_valid_connection() if conn_dict: - #conn_dict = None + # conn_dict = None series_service = self.createService(conn_dict) - #conn_dict = self.service_manager.get_current_conn_dict() + # conn_dict = self.service_manager.get_current_conn_dict() if self.servicesValid(series_service): self.service_manager.add_connection(conn_dict) @@ -156,7 +154,6 @@ def _init_database(self, quit_if_cancel=True, newConnection= ''): return series_service - def servicesValid(self, service, displayMsg=True): """ @@ -169,13 +166,14 @@ def servicesValid(self, service, displayMsg=True): ## Test if Series Catalog is empty if not service.get_used_sites(): if displayMsg: - msg = wx.MessageDialog(None, 'Series Catalog cannot be empty. Please enter in a new database connection', - 'Series Catalog is empty', wx.OK | wx.ICON_ERROR ) + msg = wx.MessageDialog(None, + 'Series Catalog cannot be empty. Please enter in a new database connection', + 'Series Catalog is empty', wx.OK | wx.ICON_ERROR) msg.ShowModal() valid = False # @TODO If Jeff runs into other issues with services not being available, we can simply test different services here - #if not service.get_all_variables(): + # if not service.get_all_variables(): # valid = False return valid @@ -186,7 +184,7 @@ def on_about_request(self, event): def MacReopenApp(self): """Called when the doc icon is clicked, and ???""" - try: # it's possible for this event to come when the frame is closed + try: # it's possible for this event to come when the frame is closed self.GetTopWindow().Raise() except: pass @@ -230,9 +228,8 @@ def _init_ctrls(self, series_service): ################ Series Selection Panel ################## logger.debug("Loading Series Selector ...") - - self.pnlSelector = FrmSeriesSelector(self.pnlDocking, series_service, plot=self.pnlPlot, taskserver=self.taskserver, memdb = self.memDB) - + self.pnlSelector = FrmSeriesSelector(self.pnlDocking, series_service, plot=self.pnlPlot, + taskserver=self.taskserver, memdb=self.memDB) ####################grid Table View################## logger.debug("Loading DataTable ...") @@ -244,21 +241,18 @@ def _init_ctrls(self, series_service): self.txtPythonConsole = ODMToolsConsole(parent=self.pnlDocking, size=wx.Size(200, 200)) wx.CallAfter(self._postStartup) - - logger.debug("Loading Python Script ...") self.txtPythonScript = pnlScript(name=u'txtPython', parent=self, - size=wx.Size(200, 200)) + size=wx.Size(200, 200)) self.Bind(wx.EVT_CLOSE, self.onClose) - Publisher.subscribe(self.onDocking, ("adjust.Docking")) Publisher.subscribe(self.onPlotSelection, ("select.Plot")) Publisher.subscribe(self.onExecuteScript, ("execute.script")) Publisher.subscribe(self.onChangeDBConn, ("change.dbConfig")) Publisher.subscribe(self.onSetScriptTitle, ("script.title")) - #.subscribe(self.onSetScriptTitle, ("script.title")) + # .subscribe(self.onSetScriptTitle, ("script.title")) Publisher.subscribe(self.onClose, ("onClose")) Publisher.subscribe(self.addEdit, ("selectEdit")) Publisher.subscribe(self.stopEdit, ("stopEdit")) @@ -273,31 +267,31 @@ def _init_aui_manager(self): self._mgr.AddPane(self.pnlPlot, aui.AuiPaneInfo().CenterPane() .Name("Plot").Caption("Plot").MaximizeButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.dataTable, aui.AuiPaneInfo().Right().Name("Table"). Show(show=False).Caption('Table View').MinSize(wx.Size(200, 200)).Floatable().Movable(). Position(1).MinimizeButton(True).MaximizeButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.pnlSelector, aui.AuiPaneInfo().Bottom().Name("Selector").MinSize(wx.Size(50, 200)). Movable().Floatable().Position(0).MinimizeButton(True).MaximizeButton(True).CloseButton(True) .DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.txtPythonScript, aui.AuiPaneInfo().Caption('Script'). Name("Script").Movable().Floatable().Right() .MinimizeButton(True).MaximizeButton(True).FloatingSize(size=(400, 400)) .CloseButton(True).Float().FloatingPosition(pos=(self.Position)) .Hide().CloseButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.txtPythonConsole, aui.AuiPaneInfo().Caption('Python Console'). Name("Console").FloatingSize(size=(300, 400)).MinimizeButton( True).Movable().Floatable().MaximizeButton(True).CloseButton(True).Float() .FloatingPosition(pos=(self.Position)).Show(show=False).DestroyOnClose(False) - ) + ) ## TODO Fix loadingDockingSettings as it doesn't load it correctly. @@ -351,7 +345,6 @@ def getDBService(self): def onPlotSelection(self, value): self.pnlPlot.selectPlot(value) - def onSetScriptTitle(self, title): scriptPane = self._mgr.GetPane(self.txtPythonScript) scriptPane.Caption(title) @@ -361,7 +354,8 @@ def onSetScriptTitle(self, title): def addEdit(self, event): - with wx.BusyInfo("Please wait for a moment while ODMTools fetches the data and stores it in our database", parent=self): + with wx.BusyInfo("Please wait for a moment while ODMTools fetches the data and stores it in our database", + parent=self): logger.debug("Beginning editing") isSelected, seriesID = self.pnlSelector.onReadyToEdit() @@ -407,16 +401,15 @@ def addEdit(self, event): logger.debug("Recording? %s" % self.record_service._record) - #self.record_service = None + # self.record_service = None self.txtPythonConsole.shell.run("edit_service = app.TopWindow.record_service", prompt=False, verbose=False) self.txtPythonConsole.shell.run("series_service = edit_service.get_series_service()", prompt=False, verbose=False) - #from meliae import scanner - #scanner.dump_all_objects("edit_plotting.dat") + # from meliae import scanner + # scanner.dump_all_objects("edit_plotting.dat") logger.info("Finished Setting up Editing Series: %s " % seriesID) - def stopEdit(self, event): self.pnlSelector.stopEdit() @@ -426,7 +419,6 @@ def stopEdit(self, event): self.record_service = None self._ribbon.toggleEditButtons(False) - def getRecordService(self): return self.record_service @@ -444,9 +436,7 @@ def onChangeDBConn(self, event): if self._ribbon.getEditStatus(): self.stopEdit(event=None) - if value == wx.ID_OK: - series_service = self.createService(newConnection) self.pnlSelector.resetDB(series_service) self.refreshConnectionInfo() @@ -462,8 +452,10 @@ def createService(self, conn_dict=""): :return: """ + series_service= self.service_manager.get_series_service(conn_dict=conn_dict)#=connection) series_service.refreshDB(conn_dict['version']) + return series_service def getServiceManager(self): @@ -503,7 +495,7 @@ def onClose(self, event): print "error saving docking data" self._mgr.UnInit() - + # Shut down processes running in background if self.taskserver.numprocesses > 0 and self.taskserver.anyAlive: busy = wx.BusyInfo("Closing ODMTools ...", parent=self) From 9ffdaf102285c3d28d32d475bac66162509d5053 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Thu, 12 Nov 2015 13:39:40 -0700 Subject: [PATCH 012/158] fix issue printing version # to dbconfig dropdown --- odmtools/controller/frmDBConfig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py index 6f9b539..0553719 100755 --- a/odmtools/controller/frmDBConfig.py +++ b/odmtools/controller/frmDBConfig.py @@ -127,8 +127,9 @@ def set_field_values(self): self.txtServer.SetValue(conn['address']) self.txtDBName.SetValue(conn['db']) self.txtUser.SetValue(conn['user']) - self.cbVersion.SetValue(conn['version']) + self.cbVersion.SetValue(str(conn['version'])) for k, v in self.choices.iteritems(): if v == conn['engine']: self.cbDatabaseType.SetValue(k) + From 324ae0e6b8acfff52b174b3b730e9a9b1f3b74ca Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 20 Sep 2016 17:12:34 -0600 Subject: [PATCH 013/158] #293 Updating the series_service and cv_servic. Also fixed some warnings --- odmtools/controller/olvDataTable.py | 3 +- odmtools/gui/plotProbability.py | 5 +- odmtools/odmdata/__init__.py | 2 +- odmtools/odmdata/session_factory.py | 114 +++++++++---------- odmtools/odmservices/cv_service.py | 140 ++++++++++++++++------- odmtools/odmservices/series_service.py | 143 ++++-------------------- odmtools/odmservices/service_manager.py | 9 +- 7 files changed, 189 insertions(+), 227 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 58110d1..9c9ef48 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -31,7 +31,8 @@ def init(self, memDB): self.SetColumns(columns) self.dataframe = self.memDB.getDataValuesDF() sort_by_index = list(self.dataframe.columns).index("LocalDateTime") - self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) + # self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) # sort() is depecrated + self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) self.dataObjects = self.dataframe.values.tolist() self.SetObjectGetter(self.ObjectGetter) diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index b875a49..1cc1f38 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -136,8 +136,9 @@ def updatePlot(self): #self.prob.append( #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) - xValues = oneSeries.Probability.xAxis.order().values - yValues = oneSeries.Probability.yAxis.order().values + # xValues = oneSeries.Probability.xAxis.order().values + xValues = oneSeries.Probability.xAxis.sort_values().values + yValues = oneSeries.Probability.yAxis.sort_values().values ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index dfb89a2..dd2a352 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -14,7 +14,7 @@ from sample_medium_cv import SampleMediumCV from sample_type_cv import SampleTypeCV from series import Series -from session_factory import SessionFactory +# from session_factory import SessionFactory from site import Site from site_type_cv import SiteTypeCV from source import Source diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py index 0d2d488..8afda48 100644 --- a/odmtools/odmdata/session_factory.py +++ b/odmtools/odmdata/session_factory.py @@ -1,57 +1,57 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - - -class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_size=20) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - - ''' - # Removing pool_timeout and max_overflow allowed the tests to pass - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, pool_size=20, max_overflow=0) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - ''' - - ''' - # Old code - class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, - #pool_size=20, - pool_recycle=3600) - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - ''' - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) - self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) - self.my_test_Session = sessionmaker(bind=self.my_test_engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - +# from sqlalchemy import create_engine +# from sqlalchemy.orm import sessionmaker +# +# +# class SessionFactory(): +# def __init__(self, connection_string, echo): +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_size=20) +# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'connect_timeout': 1}) +# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'timeout': 1}) +# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'connect_timeout': 1}) +# +# ''' +# # Removing pool_timeout and max_overflow allowed the tests to pass +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, pool_size=20, max_overflow=0) +# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) +# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) +# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) +# ''' +# +# ''' +# # Old code +# class SessionFactory(): +# def __init__(self, connection_string, echo): +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, +# #pool_size=20, +# pool_recycle=3600) +# +# # Create session maker +# self.Session = sessionmaker(bind=self.engine) +# +# def get_session(self): +# return self.Session() +# +# def __repr__(self): +# return "" % (self.engine) +# ''' +# +# # Create session maker +# self.Session = sessionmaker(bind=self.engine) +# self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) +# self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) +# self.my_test_Session = sessionmaker(bind=self.my_test_engine) +# +# def get_session(self): +# return self.Session() +# +# def __repr__(self): +# return "" % (self.engine) +# diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index 24b5e81..ce42cb9 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -1,5 +1,5 @@ # CV imports -from odmtools.odmdata import SessionFactory +# from odmtools.odmdata import SessionFactory from odmtools.odmdata import VerticalDatumCV from odmtools.odmdata import SiteTypeCV from odmtools.odmdata import VariableNameCV @@ -16,69 +16,79 @@ from odmtools.odmdata import Qualifier from odmtools.odmdata import Unit from sqlalchemy import not_ +from odm2api.ODMconnection import SessionFactory +from odm2api.ODM2.services.readService import ReadODM2 -class CVService(): +class CVService(): # change to readSerivice() # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() + self._session_factory = SessionFactory(connection_string=connection_string, echo=debug) + self._edit_session = self._session_factory.getSession() self._debug = debug + self.read_service = ReadODM2(session_factory=self._session_factory, debug=self._debug) # Controlled Vocabulary get methods + # Returns a list of all terms in the Controller Vocabulary (CV) - #return a list of all terms in the cv - def get_vertical_datum_cvs(self): - result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() - return result + # From ODM1 -> ODM2 Qualifier was changed to Annotations + def get_annotations(self, type): + return self.read_service.getAnnotations(type=type) - def get_samples(self): - result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() + def get_censor_code_cvs(self): + result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() return result - def get_site_type_cvs(self): - result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() + def get_data_type_cvs(self): + result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() return result - def get_variable_name_cvs(self): - result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() + def get_general_category_cvs(self): + result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() return result def get_offset_type_cvs(self): - result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() - return result + # result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() + # return result + return self.read_service.getCVs(type="Spatial Offset Type") - def get_speciation_cvs(self): - result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() - return result + # From ODM1 -> ODM2 Quality Controlled Level was changed to Processing Level + def get_all_processing_levels(self): + self.read_service.getProcessingLevels() - def get_sample_medium_cvs(self): - result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() - return result + def get_processing_level_by_id(self, id): + self.read_service.getProcessingLevels(ids=id) - def get_value_type_cvs(self): - result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() - return result + def get_processing_level_by_code(self, code): + self.read_service.getProcessingLevels(codes=code) - def get_data_type_cvs(self): - result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() + def get_samples(self): + result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() return result - def get_general_category_cvs(self): - result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() - return result + def get_sample_medium_cvs(self): + # result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() + # return result + return self.read_service.getCVs(type="Medium") - def get_censor_code_cvs(self): - result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() - return result + def get_site_type_cvs(self): + # result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() + # return result + return self.read_service.getCVs(type="Site Type") + + def get_speciation_cvs(self): + # result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() + # return result + return self.read_service.getCVs(type="Speciation") def get_sample_type_cvs(self): result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() return result def get_units(self): - result = self._edit_session.query(Unit).all() - return result + # result = self._edit_session.query(Unit).all() + # return result + self.read_service.getUnits(ids=None, name=None, type=None) def get_units_not_uni(self): result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() @@ -90,9 +100,63 @@ def get_units_names(self): # return a single cv def get_unit_by_name(self, unit_name): - result = self._edit_session.query(Unit).filter_by(name=unit_name).first() - return result + # result = self._edit_session.query(Unit).filter_by(name=unit_name).first() + # return result + return self.read_service.getUnits(name=unit_name) def get_unit_by_id(self, unit_id): - result = self._edit_session.query(Unit).filter_by(id=unit_id).first() + # result = self._edit_session.query(Unit).filter_by(id=unit_id).first() + # return result + return self.read_service.getUnits(ids=unit_id) + + def get_value_type_cvs(self): + result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() return result + + def get_variable_name_cvs(self): + # result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() + # return result + return self.read_service.getCVs(type="Variable Name") + + def get_vertical_datum_cvs(self): + # result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() + # return result + return self.read_service.getCVs("Elevation Datum") + + def get_all_variables(self): + return self.read_service.getVariables() + + def get_variable_by_id(self, id): + return self.read_service.getVariables(ids=id) + + def get_variable_by_code(self, code): + return self.read_service.getVariables(codes=code) + + # def get_all_qualifiers(self): + # """ + # + # :return: List[Qualifiers] + # """ + # result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() + # return result + # + # def get_qualifier_by_code(self, code): + # """ + # + # :return: Qualifiers + # """ + # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + # return result + # + # def get_qualifiers_by_series_id(self, series_id): + # """ + # + # :param series_id: + # :return: + # """ + # subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( + # Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() + # return self._edit_session.query(Qualifier).join(subquery).distinct().all() + + + diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 078e6fe..0a05800 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,10 +1,6 @@ import logging - - from sqlalchemy import distinct, func - - -from odmtools.odmdata import SessionFactory +from odm2api.ODMconnection import SessionFactory # from odmtools.odmdata import SessionFactory from odmtools.odmdata import Site from odmtools.odmdata import Variable from odmtools.odmdata import Unit @@ -18,20 +14,22 @@ from odmtools.odmdata import ODMVersion from odmtools.common.logger import LoggerTool import pandas as pd +from odm2api.ODM2.services.createService import CreateODM2 # tool = LoggerTool() # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') +logger = logging.getLogger('main') -class SeriesService(): +class SeriesService(): # Change to createService # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() + self._session_factory = SessionFactory(connection_string=connection_string, echo=debug) + self._edit_session = self._session_factory.getSession() self._debug = debug + self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks + self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks def get_db_version(self): return self._edit_session.query(ODMVersion).first().version_number @@ -59,7 +57,7 @@ def get_used_sites(self): try: site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] except: - site_ids = None + return None if not site_ids: return None @@ -105,35 +103,6 @@ def get_used_variables(self): return Variables - def get_all_variables(self): - """ - - :return: List[Variables] - """ - return self._edit_session.query(Variable).all() - - def get_variable_by_id(self, variable_id): - """ - - :param variable_id: int - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() - except: - return None - - def get_variable_by_code(self, variable_code): - """ - - :param variable_code: str - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() - except: - return None - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits """ Finds all of variables at a site @@ -152,79 +121,6 @@ def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeU return variables - # Unit methods - def get_all_units(self): - """ - - :return: List[Units] - """ - return self._edit_session.query(Unit).all() - - def get_unit_by_name(self, unit_name): - """ - - :param unit_name: str - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None - - def get_unit_by_id(self, unit_id): - """ - - :param unit_id: int - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() - except: - return None - - - def get_all_qualifiers(self): - """ - - :return: List[Qualifiers] - """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result - - def get_qualifier_by_code(self, code): - """ - - :return: Qualifiers - """ - result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - return result - - def get_qualifiers_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() - - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() - - def get_qcl_by_id(self, qcl_id): - try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() - except: - return None - - def get_qcl_by_code(self, qcl_code): - try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() - except: - return None - # Method methods def get_all_methods(self): return self._edit_session.query(Method).all() @@ -567,8 +463,8 @@ def create_method(self, description, link): if link is not None: meth.link = link - self._edit_session.add(meth) - self._edit_session.commit() + self.create_service.createMethod(method=meth) + return meth def create_variable_by_var(self, var): @@ -577,12 +473,14 @@ def create_variable_by_var(self, var): :param var: Variable Object :return: """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None + # try: + # self._edit_session.add(var) + # self._edit_session.commit() + # return var + # except: + # return None + self.create_service.createVariable(var) + return var def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, @@ -618,8 +516,7 @@ def create_variable( var.general_category = general_category var.no_data_value = no_data_value - self._edit_session.add(var) - self._edit_session.commit() + self.create_variable_by_var(var) return var def create_qcl(self, code, definition, explanation): diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 1ad5ff1..c106394 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -13,7 +13,7 @@ from odmtools.controller import EditTools from export_service import ExportService from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata.session_factory import SessionFactory +from odm2api.ODMconnection import SessionFactory #from odmtools.odmdata.session_factory import SessionFactory # tool = LoggerTool() @@ -100,12 +100,11 @@ def add_connection(self, conn_dict): def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) if 'mssql' in connection_string: - s.ms_test_Session().execute("Select top 1 VariableCode From Variables") + s.test_Session().execute("Select top 1 VariableCode From Variables") elif 'mysql' in connection_string: - s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') + s.test_Session().execute('Select "VariableCode" From Variables Limit 1') elif 'postgresql' in connection_string: - #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + s.test_Session().execute('Select "VariableCode" From "Variables" Limit 1') return True def test_connection(self, conn_dict): From cc7709d14ec2a6d47c4821756f1a4c0de790e520 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 20 Sep 2016 18:35:41 -0600 Subject: [PATCH 014/158] #293 Finishing up the get and create functions in series_service --- odmtools/odmservices/cv_service.py | 64 +++++---------- odmtools/odmservices/series_service.py | 107 ++++++++----------------- 2 files changed, 53 insertions(+), 118 deletions(-) diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index ce42cb9..b1a01b2 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -29,7 +29,6 @@ def __init__(self, connection_string="", debug=False): self.read_service = ReadODM2(session_factory=self._session_factory, debug=self._debug) # Controlled Vocabulary get methods - # Returns a list of all terms in the Controller Vocabulary (CV) # From ODM1 -> ODM2 Qualifier was changed to Annotations def get_annotations(self, type): @@ -48,14 +47,21 @@ def get_general_category_cvs(self): return result def get_offset_type_cvs(self): - # result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() - # return result return self.read_service.getCVs(type="Spatial Offset Type") # From ODM1 -> ODM2 Quality Controlled Level was changed to Processing Level def get_all_processing_levels(self): self.read_service.getProcessingLevels() + def get_all_method(self): # Rename to get_method_all + return self.read_service.getMethods(ids=None, codes=None, type=None) + + def get_method_by_id(self, method_id): + return self.read_service.getMethods(ids=method_id) + + def get_method_by_description(self, code): + return self.read_service.getMethods(codes=code) + def get_processing_level_by_id(self, id): self.read_service.getProcessingLevels(ids=id) @@ -67,27 +73,29 @@ def get_samples(self): return result def get_sample_medium_cvs(self): - # result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() - # return result return self.read_service.getCVs(type="Medium") def get_site_type_cvs(self): - # result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() - # return result return self.read_service.getCVs(type="Site Type") def get_speciation_cvs(self): - # result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() - # return result return self.read_service.getCVs(type="Speciation") def get_sample_type_cvs(self): result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() return result + # From ODM1 -> ODM2 Site was changed to Sampling Feature + def get_all_sites(self): + return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) + + def get_site_by_id(self, site_id): + return self.read_service.getSamplingFeatures(ids=site_id) + + def get_timeseries_result_values(self, type): + return self.read_service.getAnnotations(type=type) + def get_units(self): - # result = self._edit_session.query(Unit).all() - # return result self.read_service.getUnits(ids=None, name=None, type=None) def get_units_not_uni(self): @@ -100,13 +108,9 @@ def get_units_names(self): # return a single cv def get_unit_by_name(self, unit_name): - # result = self._edit_session.query(Unit).filter_by(name=unit_name).first() - # return result return self.read_service.getUnits(name=unit_name) def get_unit_by_id(self, unit_id): - # result = self._edit_session.query(Unit).filter_by(id=unit_id).first() - # return result return self.read_service.getUnits(ids=unit_id) def get_value_type_cvs(self): @@ -114,13 +118,9 @@ def get_value_type_cvs(self): return result def get_variable_name_cvs(self): - # result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() - # return result return self.read_service.getCVs(type="Variable Name") def get_vertical_datum_cvs(self): - # result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() - # return result return self.read_service.getCVs("Elevation Datum") def get_all_variables(self): @@ -132,31 +132,7 @@ def get_variable_by_id(self, id): def get_variable_by_code(self, code): return self.read_service.getVariables(codes=code) - # def get_all_qualifiers(self): - # """ - # - # :return: List[Qualifiers] - # """ - # result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - # return result - # - # def get_qualifier_by_code(self, code): - # """ - # - # :return: Qualifiers - # """ - # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - # return result - # - # def get_qualifiers_by_series_id(self, series_id): - # """ - # - # :param series_id: - # :return: - # """ - # subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - # Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - # return self._edit_session.query(Qualifier).join(subquery).distinct().all() + diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 0a05800..7f6b136 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -29,7 +29,8 @@ def __init__(self, connection_string="", debug=False): self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) def reset_session(self): - self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks + # self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks + self._edit_session = self.create_service.getSession() def get_db_version(self): return self._edit_session.query(ODMVersion).first().version_number @@ -40,15 +41,6 @@ def get_db_version(self): # ##################### - # Site methods - def get_all_sites(self): - """ - - :return: List[Sites] - """ - return self._edit_session.query(Site).order_by(Site.code).all() - - def get_used_sites(self): """ Return a list of all sites that are being referenced in the Series Catalog Table @@ -68,18 +60,6 @@ def get_used_sites(self): return Sites - - def get_site_by_id(self, site_id): - """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites - """ - try: - return self._edit_session.query(Site).filter_by(id=site_id).first() - except: - return None - # Variables methods def get_used_variables(self): """ @@ -103,43 +83,6 @@ def get_used_variables(self): return Variables - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] - """ - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( - site_code=site_code).all()] - except: - var_ids = None - - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return variables - - # Method methods - def get_all_methods(self): - return self._edit_session.query(Method).all() - - def get_method_by_id(self, method_id): - try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() - except: - result = None - return result - - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() - except: - result = None - logger.error("method not found") - return result - def get_offset_types_by_series_id(self, series_id): """ @@ -215,6 +158,31 @@ def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass + def get_all_qualifiers(self): + """ + + :return: List[Qualifiers] + """ + result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() + return result + + def get_qualifier_by_code(self, code): + """ + + :return: Qualifiers + """ + result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + return result + + def get_qualifiers_by_series_id(self, series_id): + """ + + :param series_id: + :return: + """ + subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( + Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() + return self._edit_session.query(Qualifier).join(subquery).distinct().all() #Data Value Methods def get_values_by_series(self, series_id): @@ -430,7 +398,6 @@ def save_values(self, values): def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): """ - :param data_values: :param site_id: :param variable_id: @@ -451,9 +418,8 @@ def create_new_series(self, data_values, site_id, variable_id, method_id, source self._edit_session.commit() return series - def create_method(self, description, link): + def create_method(self, description, link): # DONE """ - :param description: :param link: :return: @@ -467,25 +433,19 @@ def create_method(self, description, link): return meth - def create_variable_by_var(self, var): + def create_variable_by_var(self, var): # DONE """ - :param var: Variable Object :return: """ - # try: - # self._edit_session.add(var) - # self._edit_session.commit() - # return var - # except: - # return None + self.create_service.createVariable(var) return var def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): + general_category, no_data_value): # DONE """ :param code: @@ -519,7 +479,7 @@ def create_variable( self.create_variable_by_var(var) return var - def create_qcl(self, code, definition, explanation): + def create_qcl(self, code, definition, explanation): # DONE """ :param code: @@ -532,8 +492,7 @@ def create_qcl(self, code, definition, explanation): qcl.definition = definition qcl.explanation = explanation - self._edit_session.add(qcl) - self._edit_session.commit() + self.create_service.createProcessingLevel(qcl) return qcl From 9039513f78a458702a8e2944509173e9fc8de4a9 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 22 Sep 2016 17:00:22 -0600 Subject: [PATCH 015/158] Undoing the last two commits I made changes I was not supposed to. I understand the project better and will restart the process --- odmtools/controller/frmAddPoints.py | 0 odmtools/controller/frmBulkInsert.py | 0 odmtools/controller/frmDBConfig.py | 0 odmtools/controller/frmSeriesSelector.py | 1244 ++-- odmtools/controller/logicCellEdit.py | 0 odmtools/controller/olvAddPoint.py | 0 odmtools/controller/olvDataTable.py | 3 +- odmtools/gui/frmFlagValues.py | 0 odmtools/gui/frmODMTools.py | 0 odmtools/gui/mnuRibbon.py | 0 odmtools/gui/plotProbability.py | 5 +- odmtools/gui/plotTimeSeries.py | 0 odmtools/lib/ObjectListView/ObjectListView.py | 0 odmtools/lib/oldOlv/CellEditor.py | 1096 ++-- odmtools/lib/oldOlv/Filter.py | 276 +- odmtools/lib/oldOlv/ListCtrlPrinter.py | 5594 ++++++++--------- odmtools/lib/oldOlv/OLVEvent.py | 558 +- odmtools/lib/oldOlv/ObjectListView.py | 0 odmtools/lib/oldOlv/WordWrapRenderer.py | 464 +- odmtools/lib/oldOlv/t.py | 456 +- odmtools/odmdata/__init__.py | 2 +- odmtools/odmdata/session_factory.py | 114 +- odmtools/odmservices/cv_service.py | 116 +- odmtools/odmservices/series_service.py | 236 +- odmtools/odmservices/service_manager.py | 9 +- odmtools/view/clsAddPoints.py | 0 odmtools/view/clsSeriesSelector.py | 566 +- 27 files changed, 5421 insertions(+), 5318 deletions(-) mode change 100755 => 100644 odmtools/controller/frmAddPoints.py mode change 100755 => 100644 odmtools/controller/frmBulkInsert.py mode change 100755 => 100644 odmtools/controller/frmDBConfig.py mode change 100755 => 100644 odmtools/controller/frmSeriesSelector.py mode change 100755 => 100644 odmtools/controller/logicCellEdit.py mode change 100755 => 100644 odmtools/controller/olvAddPoint.py mode change 100755 => 100644 odmtools/gui/frmFlagValues.py mode change 100755 => 100644 odmtools/gui/frmODMTools.py mode change 100755 => 100644 odmtools/gui/mnuRibbon.py mode change 100755 => 100644 odmtools/gui/plotTimeSeries.py mode change 100755 => 100644 odmtools/lib/ObjectListView/ObjectListView.py mode change 100755 => 100644 odmtools/lib/oldOlv/ObjectListView.py mode change 100755 => 100644 odmtools/odmservices/service_manager.py mode change 100755 => 100644 odmtools/view/clsAddPoints.py mode change 100755 => 100644 odmtools/view/clsSeriesSelector.py diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py old mode 100755 new mode 100644 diff --git a/odmtools/controller/frmBulkInsert.py b/odmtools/controller/frmBulkInsert.py old mode 100755 new mode 100644 diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py old mode 100755 new mode 100644 diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py old mode 100755 new mode 100644 index 4563190..c06324e --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -1,622 +1,622 @@ -import os -import wx -import logging - -from wx.lib.pubsub import pub as Publisher -from odmtools.lib.ObjectListView.Filter import TextSearch, Chain -import odmtools.gui.frmQueryBuilder as frmQueryBuilder - -from odmtools.common.logger import LoggerTool -from odmtools.odmdata import MemoryDatabase -from odmtools.view import clsSeriesSelector - -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -__author__ = 'Jacob' - -class FrmSeriesSelector(clsSeriesSelector.ClsSeriesSelector): - """ - - """ - def __init__(self, *args, **kwargs): - """ - - """ - - self.taskserver = kwargs.pop("taskserver") - self.memDB = kwargs.pop("memdb") - self.pnlPlot = kwargs.pop("plot") - - clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) - - def initPubSub(self): - #Publisher.subscribe(self.onEditButton, ("selectEdit")) - Publisher.subscribe(self.refreshSeries, "refreshSeries") - - def resetDB(self, series_service): - """ - - :param series_service: - :return: - """ - - if not self.rbAll.GetValue(): - #self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) - wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) - self.rbAll.SetValue(True) - - #####INIT DB Connection - self.series_service = series_service - #self.refreshSeries() - self.cbVariables.Clear() - self.cbSites.Clear() - - self.siteList = None - self.varList = None - - self.initTableSeries() - self.initSVBoxes() - self.Layout() - - def initTableSeries(self): - """Set up columns and objects to be used in the objectlistview to be visible in the series selector - - :return: - """ - try: - self.memDB.set_series_service(self.series_service) - - object = self.series_service.get_all_series() - - if object: - self.tblSeries.SetObjects(object) - else: - self.tblSeries.SetObjects(None) - - except AttributeError as e: - logger.error(e) - #self.tblSeries.SaveObject(object) - - def refreshTableSeries(self, db): - """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' - for plotting/editing - - :return: - """ - chcklist= self.tblSeries.GetCheckedObjects() - - - self.memDB.set_series_service(db) - object = self.series_service.get_all_series() - #checkedObjs = self.tblSeries.GetCheckedObjects() - idList = [x.id for x in self.tblSeries.modelObjects] - - for x in object: - if x.id not in idList: - self.tblSeries.AddObject(x) - - - for c in chcklist: - self.tblSeries.SetCheckState(c, True) - - - #for x in checkedObjs: - # super(FastObjectListView, self.tblSeries).SetCheckState(x, True) - - def refreshSeries(self): - """ - - :return: - """ - chcklist= self.tblSeries.GetCheckedObjects() - self.series_service = None - - self.series_service = self.parent.Parent.createService() - #self.refreshTableSeries(self.dbservice) - self.resetDB(self.series_service) - - for c in chcklist: - for val in self.tblSeries.GetObjects(): - if c == val: - self.tblSeries.SetCheckState(val, True) - break - - logger.debug("Repopulate Series Selector") - - - - def initSVBoxes(self): - """ - - :return: - """ - - self.site_code = None - self.variable_code = None - - #####INIT drop down boxes for Simple Filter - try: - self.siteList = self.series_service.get_used_sites() - for site in self.siteList: - self.cbSites.Append(site.code + '-' + site.name) - self.cbSites.SetSelection(0) - self.site_code = self.siteList[0].code - - self.varList = self.series_service.get_used_variables() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - except AttributeError as e: - logger.error(e) - - def OnTableRightDown(self, event): - """Right click down menu - - :param event: - :return: - """ - - # build pop-up menu for right-click display - self.selectedIndex = event.m_itemIndex - #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) - self.selectedID = self.tblSeries.GetSelectedObject().id - - # print self.selectedID - popup_edit_series = wx.NewId() - popup_plot_series = wx.NewId() - popup_export_data = wx.NewId() - popup_series_refresh = wx.NewId() - popup_clear_selected = wx.NewId() - - popup_export_metadata = wx.NewId() - popup_select_all = wx.NewId() - popup_select_none = wx.NewId() - - popup_menu = wx.Menu() - plotItem = popup_menu.Append(popup_plot_series, 'Plot') - editItem = popup_menu.Append(popup_edit_series, 'Edit') - - self.Bind(wx.EVT_MENU, self.onRightPlot, plotItem) - # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited - self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) - # TODO @jmeline will refresh and clear selected as an enhancement - #self.Bind(wx.EVT_MENU, self.onRightRefresh, popup_menu.Append(popup_series_refresh, 'Refresh')) - #self.Bind(wx.EVT_MENU, self.onRightClearSelected, popup_menu.Append(popup_series_refresh, 'Clear Selected')) - - popup_menu.AppendSeparator() - self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) - self.Bind(wx.EVT_MENU, self.onRightExMeta, popup_menu.Append(popup_export_metadata, 'Export MetaData')) - - if self.isEditing: - popup_menu.Enable(popup_edit_series, False) - - self.tblSeries.PopupMenu(popup_menu) - event.Skip() - - def onPaneChanged(self, event=None): - #if event: - # print 'wx.EVT_COLLAPSIBLEPANE_CHANGED: %s\n' % event.Collapsed - self.Layout() - - def onRbAdvancedRadiobutton(self, event): - """ - - :param event: - :return: - """ - - self.cpnlSimple.Collapse(True) - self.Layout() - series_filter = frmQueryBuilder.frmQueryBuilder(self) - self.filterlist = series_filter.ShowModal() - event.Skip() - - def onRbAllRadiobutton(self, event): - """ - - :param event: - :return: - """ - - if self.checkSite.GetValue() or self.checkVariable.GetValue(): - self.checkSite.SetValue(False) - self.checkVariable.SetValue(False) - - #logger.debug("onRbAllRadioButton called! ") - self.cpnlSimple.Collapse(True) - self.Layout() - self.setFilter() - event.Skip() - - def onRbSimpleRadiobutton(self, event): - """ - - :param event: - :return: - """ - - self.cpnlSimple.Expand() - self.Layout() - - - ## - if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): - self.setFilter() - return - - self.setFilter(self.site_code, self.variable_code) - - event.Skip() - - def onRightPlot(self, event): - """ - - :param event: - :return: - """ - object = self.tblSeries.GetSelectedObject() - self.tblSeries.ToggleCheck(object) - self.onReadyToPlot(event) - event.Skip() - - def onRightEdit(self, event): - """ - - :param event: - :return: - """ - Publisher.sendMessage(("selectEdit"), event=event) - if self.isEditing: - Publisher.sendMessage("toggleEdit", checked=True) - event.Skip() - - # allows user to right-click refresh the Series Selector - def onRightRefresh(self, event): - """ - - :param event: - :return: - """ - self.refreshSeries() - event.Skip() - - def onRightClearSelected(self, event): - """ - - :param event: - :return: - """ - event.Skip() - - - - def onRightExData(self, event): - """ - - :param event: - :return: - """ - dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.csv", wx.SAVE | wx.OVERWRITE_PROMPT) - if dlg.ShowModal() == wx.ID_OK: - full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) - - #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) - series_id = self.tblSeries.GetSelectedObject().id - self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) - self.Close() - - dlg.Destroy() - - event.Skip() - - def onRightExMeta(self, event): - """ - - :param event: - :return: - """ - dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.xml", wx.SAVE | wx.OVERWRITE_PROMPT) - if dlg.ShowModal() == wx.ID_OK: - full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) - - self.selectedIndex = self.tblSeries.GetSelectedObject().id - #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) - #print "series_id", series_id - - self.export_service.export_series_metadata(self.selectedIndex, full_path) - self.Close() - - dlg.Destroy() - - event.Skip() - - def onCbSitesCombobox(self, event): - """ - - :param event: - :return: - """ - - if self.checkSite.GetValue(): - self.site_code = self.siteList[event.GetSelection()].code - self.varList = self.series_service.get_variables_by_site_code(self.site_code) - - self.cbVariables.Clear() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - - if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): - self.variable_code = None - - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - event.Skip() - - def onCbVariablesCombobox(self, event): - """ - - :param event: - :return: - """ - - if self.checkVariable.GetValue(): - self.variable_code = self.varList[event.GetSelection()].code - if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): - self.site_code = None - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - event.Skip() - - def siteAndVariables(self): - """ - - :return: - """ - self.site_code = self.siteList[self.cbSites.Selection].code - - self.cbVariables.Clear() - self.varList = self.series_service.get_variables_by_site_code(self.site_code) - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - - try: - self.variable_code = self.varList[self.cbVariables.Selection].code - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - self.cbVariables.Enabled = True - self.cbSites.Enabled = True - except IndexError as i: - logger.error(i) - pass - - def siteOnly(self): - """ - - :return: - """ - self.cbVariables.Enabled = False - self.cbSites.Enabled = True - self.variable_code = None - - self.site_code = self.siteList[self.cbSites.Selection].code - self.setFilter(site_code=self.site_code) - - def variableOnly(self): - """ - - :return: - """ - self.site_code = None - self.cbVariables.Clear() - self.varList = self.series_service.get_used_variables() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - self.cbSites.Enabled = False - self.cbVariables.Enabled = True - - self.variable_code = self.varList[0].code - - self.setFilter(var_code=self.variable_code) - - def onCheck(self, event): - """ - - :param event: - :return: - """ - # self.tableSeries.DeleteAllItems() - if self.rbAll.GetValue(): - #logger.debug("Called!") - self.rbAll.SetValue(False) - self.rbSimple.SetValue(True) - - if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): - self.setFilter() - self.cbSites.Enabled = False - self.cbVariables.Enabled = False - - elif self.checkSite.GetValue(): - if self.checkVariable.GetValue(): - self.siteAndVariables() - else: - self.siteOnly() - else: - if self.checkVariable.GetValue(): - self.variableOnly() - else: - self.cbSites.Enabled = False - self.cbVariables.Enabled = False - event.Skip() - - def setFilter(self, site_code='', var_code='', advfilter=''): - """ - - :param site_code: - :param var_code: - :param advfilter: - :return: - """ - if site_code and var_code: - self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4],text=site_code) - self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7],text=var_code) - self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) - elif site_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4], text=site_code)) - elif var_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7], text=var_code)) - elif advfilter: - self.tblSeries.SetFilter(advfilter) - else: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:1])) - self.tblSeries.RepopulateList() - - - def onReadyToPlot(self, event): - """Plots a series selected from the series selector - - :param event: EVT_OVL_CHECK_EVENT type - """ - logger.debug("Starting to Plot") - - checkedCount = len(self.tblSeries.GetCheckedObjects()) - - Publisher.sendMessage("EnablePlotButtons", plot=0, isActive=(checkedCount > 0)) - - logger.debug("Obtain object") - try: - object = event.object - except Exception as e : - object = self.tblSeries.GetSelectedObject() - - if not self.tblSeries.IsChecked(object): - Publisher.sendMessage("removePlot", seriesID=object.id) - Publisher.sendMessage("updateCursor", deselectedObject=object) - - else: - logger.debug("Obtained object, entering addplot") - self.pnlPlot.addPlot(self.memDB, object.id) - Publisher.sendMessage("updateCursor", selectedObject=object) - - logger.debug("refreshing...") - self.Refresh() - - logger.debug("Finish Plotting") - - - #from meliae import scanner - #scanner.dump_all_objects("plot_plotting.dat") - - def getSelectedObject(self, event): - """Capture the currently selected Object to be used for editing - - :param event: wx.EVT_LIST_ITEM_FOCUSED type - """ - - logger.debug("Selecting object from Series Catalog") - - object = event.GetEventObject() - editingObject = object.innerList[object.FocusedItem] - - self.tblSeries.currentlySelectedObject = editingObject - - ## update Cursor - if self.parent.Parent.pnlPlot._seriesPlotInfo: - if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.id): - #print "Updating Cursor", editingObject.id - Publisher.sendMessage("updateCursor", selectedObject=editingObject) - - - def onReadyToEdit(self): - """Choose a series to edit from the series selector - - :return: - """ - - ovl = self.tblSeries - - object = ovl.currentlySelectedObject - if object is None: - # # Select the first one - if len(ovl.modelObjects) == 0: - logger.fatal("There are no model objects available to edit") - raise Exception() - object = ovl.modelObjects[0] - - if len(ovl.GetCheckedObjects()) <= ovl.allowedLimit: - - if object not in ovl.GetCheckedObjects(): - ovl.ToggleCheck(object) - - - - # logger.debug("Initializing DataTable") - # - # tasks = [("dataTable", self.memDB.conn)] - # self.taskserver.setTasks(tasks) - # self.taskserver.processTasks() - - self.isEditing = True - ovl.editingObject = object - ovl.RefreshObject(ovl.editingObject) - - - return True, object.id#, self.memDB - else: - isSelected = False - logger.debug("series was not checked") - val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", - wx.OK | wx.ICON_INFORMATION) - - - self.isEditing = False - ovl.editingObject = None - return False, object.id#, self.memDB - - def stopEdit(self): - """When edit button is untoggled, the editing feature closes - :return: - """ - - self.isEditing = False - chcklist= self.tblSeries.GetCheckedObjects() - - self.tblSeries.RefreshObject(self.tblSeries.editingObject) - for c in chcklist: - self.tblSeries.SetCheckState(c, True) - - self.memDB.stopEdit() - - - def isEditing(self): - """ - - :return: - """ - return self.isEditing - - - def _rowFormatter(self, listItem, object): - """Handles the formatting of rows for object list view - :param: wx.ListCtrl listitem - :param: ModelObject object - - :rtype: None - """ - ''' - if self.tblSeries.editingObject and \ - object.id == self.tblSeries.editingObject.id: - #listItem.SetTextColour(wx.Colour(255, 25, 112)) - # font type: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN - # slant: wx.NORMAL, wx.SLANT or wx.ITALIC - # weight: wx.NORMAL, wx.LIGHT or wx.BOLD - #font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL) - # use additional fonts this way ... - #font1 = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Comic Sans MS') - listItem.SetFont( - wx.Font(9, family=wx.DEFAULT, weight=wx.BOLD, style=wx.ITALIC)) - else: - listItem.SetTextColour(wx.Colour()) - listItem.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)) - ''' +import os +import wx +import logging + +from wx.lib.pubsub import pub as Publisher +from odmtools.lib.ObjectListView.Filter import TextSearch, Chain +import odmtools.gui.frmQueryBuilder as frmQueryBuilder + +from odmtools.common.logger import LoggerTool +from odmtools.odmdata import MemoryDatabase +from odmtools.view import clsSeriesSelector + +# tool = LoggerTool() +# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) +logger =logging.getLogger('main') + +__author__ = 'Jacob' + +class FrmSeriesSelector(clsSeriesSelector.ClsSeriesSelector): + """ + + """ + def __init__(self, *args, **kwargs): + """ + + """ + + self.taskserver = kwargs.pop("taskserver") + self.memDB = kwargs.pop("memdb") + self.pnlPlot = kwargs.pop("plot") + + clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) + + def initPubSub(self): + #Publisher.subscribe(self.onEditButton, ("selectEdit")) + Publisher.subscribe(self.refreshSeries, "refreshSeries") + + def resetDB(self, series_service): + """ + + :param series_service: + :return: + """ + + if not self.rbAll.GetValue(): + #self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) + wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) + self.rbAll.SetValue(True) + + #####INIT DB Connection + self.series_service = series_service + #self.refreshSeries() + self.cbVariables.Clear() + self.cbSites.Clear() + + self.siteList = None + self.varList = None + + self.initTableSeries() + self.initSVBoxes() + self.Layout() + + def initTableSeries(self): + """Set up columns and objects to be used in the objectlistview to be visible in the series selector + + :return: + """ + try: + self.memDB.set_series_service(self.series_service) + + object = self.series_service.get_all_series() + + if object: + self.tblSeries.SetObjects(object) + else: + self.tblSeries.SetObjects(None) + + except AttributeError as e: + logger.error(e) + #self.tblSeries.SaveObject(object) + + def refreshTableSeries(self, db): + """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' + for plotting/editing + + :return: + """ + chcklist= self.tblSeries.GetCheckedObjects() + + + self.memDB.set_series_service(db) + object = self.series_service.get_all_series() + #checkedObjs = self.tblSeries.GetCheckedObjects() + idList = [x.id for x in self.tblSeries.modelObjects] + + for x in object: + if x.id not in idList: + self.tblSeries.AddObject(x) + + + for c in chcklist: + self.tblSeries.SetCheckState(c, True) + + + #for x in checkedObjs: + # super(FastObjectListView, self.tblSeries).SetCheckState(x, True) + + def refreshSeries(self): + """ + + :return: + """ + chcklist= self.tblSeries.GetCheckedObjects() + self.series_service = None + + self.series_service = self.parent.Parent.createService() + #self.refreshTableSeries(self.dbservice) + self.resetDB(self.series_service) + + for c in chcklist: + for val in self.tblSeries.GetObjects(): + if c == val: + self.tblSeries.SetCheckState(val, True) + break + + logger.debug("Repopulate Series Selector") + + + + def initSVBoxes(self): + """ + + :return: + """ + + self.site_code = None + self.variable_code = None + + #####INIT drop down boxes for Simple Filter + try: + self.siteList = self.series_service.get_used_sites() + for site in self.siteList: + self.cbSites.Append(site.code + '-' + site.name) + self.cbSites.SetSelection(0) + self.site_code = self.siteList[0].code + + self.varList = self.series_service.get_used_variables() + for var in self.varList: + self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.SetSelection(0) + except AttributeError as e: + logger.error(e) + + def OnTableRightDown(self, event): + """Right click down menu + + :param event: + :return: + """ + + # build pop-up menu for right-click display + self.selectedIndex = event.m_itemIndex + #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) + self.selectedID = self.tblSeries.GetSelectedObject().id + + # print self.selectedID + popup_edit_series = wx.NewId() + popup_plot_series = wx.NewId() + popup_export_data = wx.NewId() + popup_series_refresh = wx.NewId() + popup_clear_selected = wx.NewId() + + popup_export_metadata = wx.NewId() + popup_select_all = wx.NewId() + popup_select_none = wx.NewId() + + popup_menu = wx.Menu() + plotItem = popup_menu.Append(popup_plot_series, 'Plot') + editItem = popup_menu.Append(popup_edit_series, 'Edit') + + self.Bind(wx.EVT_MENU, self.onRightPlot, plotItem) + # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited + self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) + # TODO @jmeline will refresh and clear selected as an enhancement + #self.Bind(wx.EVT_MENU, self.onRightRefresh, popup_menu.Append(popup_series_refresh, 'Refresh')) + #self.Bind(wx.EVT_MENU, self.onRightClearSelected, popup_menu.Append(popup_series_refresh, 'Clear Selected')) + + popup_menu.AppendSeparator() + self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) + self.Bind(wx.EVT_MENU, self.onRightExMeta, popup_menu.Append(popup_export_metadata, 'Export MetaData')) + + if self.isEditing: + popup_menu.Enable(popup_edit_series, False) + + self.tblSeries.PopupMenu(popup_menu) + event.Skip() + + def onPaneChanged(self, event=None): + #if event: + # print 'wx.EVT_COLLAPSIBLEPANE_CHANGED: %s\n' % event.Collapsed + self.Layout() + + def onRbAdvancedRadiobutton(self, event): + """ + + :param event: + :return: + """ + + self.cpnlSimple.Collapse(True) + self.Layout() + series_filter = frmQueryBuilder.frmQueryBuilder(self) + self.filterlist = series_filter.ShowModal() + event.Skip() + + def onRbAllRadiobutton(self, event): + """ + + :param event: + :return: + """ + + if self.checkSite.GetValue() or self.checkVariable.GetValue(): + self.checkSite.SetValue(False) + self.checkVariable.SetValue(False) + + #logger.debug("onRbAllRadioButton called! ") + self.cpnlSimple.Collapse(True) + self.Layout() + self.setFilter() + event.Skip() + + def onRbSimpleRadiobutton(self, event): + """ + + :param event: + :return: + """ + + self.cpnlSimple.Expand() + self.Layout() + + + ## + if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): + self.setFilter() + return + + self.setFilter(self.site_code, self.variable_code) + + event.Skip() + + def onRightPlot(self, event): + """ + + :param event: + :return: + """ + object = self.tblSeries.GetSelectedObject() + self.tblSeries.ToggleCheck(object) + self.onReadyToPlot(event) + event.Skip() + + def onRightEdit(self, event): + """ + + :param event: + :return: + """ + Publisher.sendMessage(("selectEdit"), event=event) + if self.isEditing: + Publisher.sendMessage("toggleEdit", checked=True) + event.Skip() + + # allows user to right-click refresh the Series Selector + def onRightRefresh(self, event): + """ + + :param event: + :return: + """ + self.refreshSeries() + event.Skip() + + def onRightClearSelected(self, event): + """ + + :param event: + :return: + """ + event.Skip() + + + + def onRightExData(self, event): + """ + + :param event: + :return: + """ + dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.csv", wx.SAVE | wx.OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) + + #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) + series_id = self.tblSeries.GetSelectedObject().id + self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) + self.Close() + + dlg.Destroy() + + event.Skip() + + def onRightExMeta(self, event): + """ + + :param event: + :return: + """ + dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.xml", wx.SAVE | wx.OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) + + self.selectedIndex = self.tblSeries.GetSelectedObject().id + #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) + #print "series_id", series_id + + self.export_service.export_series_metadata(self.selectedIndex, full_path) + self.Close() + + dlg.Destroy() + + event.Skip() + + def onCbSitesCombobox(self, event): + """ + + :param event: + :return: + """ + + if self.checkSite.GetValue(): + self.site_code = self.siteList[event.GetSelection()].code + self.varList = self.series_service.get_variables_by_site_code(self.site_code) + + self.cbVariables.Clear() + for var in self.varList: + self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.SetSelection(0) + + if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): + self.variable_code = None + + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + event.Skip() + + def onCbVariablesCombobox(self, event): + """ + + :param event: + :return: + """ + + if self.checkVariable.GetValue(): + self.variable_code = self.varList[event.GetSelection()].code + if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): + self.site_code = None + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + event.Skip() + + def siteAndVariables(self): + """ + + :return: + """ + self.site_code = self.siteList[self.cbSites.Selection].code + + self.cbVariables.Clear() + self.varList = self.series_service.get_variables_by_site_code(self.site_code) + for var in self.varList: + self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.SetSelection(0) + + try: + self.variable_code = self.varList[self.cbVariables.Selection].code + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + self.cbVariables.Enabled = True + self.cbSites.Enabled = True + except IndexError as i: + logger.error(i) + pass + + def siteOnly(self): + """ + + :return: + """ + self.cbVariables.Enabled = False + self.cbSites.Enabled = True + self.variable_code = None + + self.site_code = self.siteList[self.cbSites.Selection].code + self.setFilter(site_code=self.site_code) + + def variableOnly(self): + """ + + :return: + """ + self.site_code = None + self.cbVariables.Clear() + self.varList = self.series_service.get_used_variables() + for var in self.varList: + self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.SetSelection(0) + self.cbSites.Enabled = False + self.cbVariables.Enabled = True + + self.variable_code = self.varList[0].code + + self.setFilter(var_code=self.variable_code) + + def onCheck(self, event): + """ + + :param event: + :return: + """ + # self.tableSeries.DeleteAllItems() + if self.rbAll.GetValue(): + #logger.debug("Called!") + self.rbAll.SetValue(False) + self.rbSimple.SetValue(True) + + if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): + self.setFilter() + self.cbSites.Enabled = False + self.cbVariables.Enabled = False + + elif self.checkSite.GetValue(): + if self.checkVariable.GetValue(): + self.siteAndVariables() + else: + self.siteOnly() + else: + if self.checkVariable.GetValue(): + self.variableOnly() + else: + self.cbSites.Enabled = False + self.cbVariables.Enabled = False + event.Skip() + + def setFilter(self, site_code='', var_code='', advfilter=''): + """ + + :param site_code: + :param var_code: + :param advfilter: + :return: + """ + if site_code and var_code: + self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4],text=site_code) + self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7],text=var_code) + self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) + elif site_code: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4], text=site_code)) + elif var_code: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7], text=var_code)) + elif advfilter: + self.tblSeries.SetFilter(advfilter) + else: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:1])) + self.tblSeries.RepopulateList() + + + def onReadyToPlot(self, event): + """Plots a series selected from the series selector + + :param event: EVT_OVL_CHECK_EVENT type + """ + logger.debug("Starting to Plot") + + checkedCount = len(self.tblSeries.GetCheckedObjects()) + + Publisher.sendMessage("EnablePlotButtons", plot=0, isActive=(checkedCount > 0)) + + logger.debug("Obtain object") + try: + object = event.object + except Exception as e : + object = self.tblSeries.GetSelectedObject() + + if not self.tblSeries.IsChecked(object): + Publisher.sendMessage("removePlot", seriesID=object.id) + Publisher.sendMessage("updateCursor", deselectedObject=object) + + else: + logger.debug("Obtained object, entering addplot") + self.pnlPlot.addPlot(self.memDB, object.id) + Publisher.sendMessage("updateCursor", selectedObject=object) + + logger.debug("refreshing...") + self.Refresh() + + logger.debug("Finish Plotting") + + + #from meliae import scanner + #scanner.dump_all_objects("plot_plotting.dat") + + def getSelectedObject(self, event): + """Capture the currently selected Object to be used for editing + + :param event: wx.EVT_LIST_ITEM_FOCUSED type + """ + + logger.debug("Selecting object from Series Catalog") + + object = event.GetEventObject() + editingObject = object.innerList[object.FocusedItem] + + self.tblSeries.currentlySelectedObject = editingObject + + ## update Cursor + if self.parent.Parent.pnlPlot._seriesPlotInfo: + if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.id): + #print "Updating Cursor", editingObject.id + Publisher.sendMessage("updateCursor", selectedObject=editingObject) + + + def onReadyToEdit(self): + """Choose a series to edit from the series selector + + :return: + """ + + ovl = self.tblSeries + + object = ovl.currentlySelectedObject + if object is None: + # # Select the first one + if len(ovl.modelObjects) == 0: + logger.fatal("There are no model objects available to edit") + raise Exception() + object = ovl.modelObjects[0] + + if len(ovl.GetCheckedObjects()) <= ovl.allowedLimit: + + if object not in ovl.GetCheckedObjects(): + ovl.ToggleCheck(object) + + + + # logger.debug("Initializing DataTable") + # + # tasks = [("dataTable", self.memDB.conn)] + # self.taskserver.setTasks(tasks) + # self.taskserver.processTasks() + + self.isEditing = True + ovl.editingObject = object + ovl.RefreshObject(ovl.editingObject) + + + return True, object.id#, self.memDB + else: + isSelected = False + logger.debug("series was not checked") + val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", + wx.OK | wx.ICON_INFORMATION) + + + self.isEditing = False + ovl.editingObject = None + return False, object.id#, self.memDB + + def stopEdit(self): + """When edit button is untoggled, the editing feature closes + :return: + """ + + self.isEditing = False + chcklist= self.tblSeries.GetCheckedObjects() + + self.tblSeries.RefreshObject(self.tblSeries.editingObject) + for c in chcklist: + self.tblSeries.SetCheckState(c, True) + + self.memDB.stopEdit() + + + def isEditing(self): + """ + + :return: + """ + return self.isEditing + + + def _rowFormatter(self, listItem, object): + """Handles the formatting of rows for object list view + :param: wx.ListCtrl listitem + :param: ModelObject object + + :rtype: None + """ + ''' + if self.tblSeries.editingObject and \ + object.id == self.tblSeries.editingObject.id: + #listItem.SetTextColour(wx.Colour(255, 25, 112)) + # font type: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN + # slant: wx.NORMAL, wx.SLANT or wx.ITALIC + # weight: wx.NORMAL, wx.LIGHT or wx.BOLD + #font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL) + # use additional fonts this way ... + #font1 = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Comic Sans MS') + listItem.SetFont( + wx.Font(9, family=wx.DEFAULT, weight=wx.BOLD, style=wx.ITALIC)) + else: + listItem.SetTextColour(wx.Colour()) + listItem.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)) + ''' diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py old mode 100755 new mode 100644 diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py old mode 100755 new mode 100644 diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 9c9ef48..58110d1 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -31,8 +31,7 @@ def init(self, memDB): self.SetColumns(columns) self.dataframe = self.memDB.getDataValuesDF() sort_by_index = list(self.dataframe.columns).index("LocalDateTime") - # self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) # sort() is depecrated - self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) + self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) self.dataObjects = self.dataframe.values.tolist() self.SetObjectGetter(self.ObjectGetter) diff --git a/odmtools/gui/frmFlagValues.py b/odmtools/gui/frmFlagValues.py old mode 100755 new mode 100644 diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py old mode 100755 new mode 100644 diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py old mode 100755 new mode 100644 diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 1cc1f38..b875a49 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -136,9 +136,8 @@ def updatePlot(self): #self.prob.append( #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) - # xValues = oneSeries.Probability.xAxis.order().values - xValues = oneSeries.Probability.xAxis.sort_values().values - yValues = oneSeries.Probability.yAxis.sort_values().values + xValues = oneSeries.Probability.xAxis.order().values + yValues = oneSeries.Probability.yAxis.order().values ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) diff --git a/odmtools/gui/plotTimeSeries.py b/odmtools/gui/plotTimeSeries.py old mode 100755 new mode 100644 diff --git a/odmtools/lib/ObjectListView/ObjectListView.py b/odmtools/lib/ObjectListView/ObjectListView.py old mode 100755 new mode 100644 diff --git a/odmtools/lib/oldOlv/CellEditor.py b/odmtools/lib/oldOlv/CellEditor.py index 1244941..aa71423 100644 --- a/odmtools/lib/oldOlv/CellEditor.py +++ b/odmtools/lib/oldOlv/CellEditor.py @@ -1,548 +1,548 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: CellEditor.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: -# - there has to be a better DateTimeEditor somewhere!! - -""" -The *CellEditor* module provides some editors for standard types that can be installed -in an *ObjectListView*. It also provides a *Registry* that maps between standard types -and functions that will create editors for that type. - -Cell Editors - - A cell editor can be any subclass of wx.Window provided that it supports the - following protocol: - - SetValue(self, value) - The editor should show the given value for editing - - GetValue(self) - The editor should return the value that it holds. Return None to indicate - an invalid value. The returned value should be of the correct type, i.e. - don't return a string if the editor was registered for the bool type. - - The editor should invoke FinishCellEdit() on its parent ObjectListView when it - loses focus or when the user commits the change by pressing Return or Enter. - - The editor should invoke CancelCellEdit() on its parent ObjectListView when - the user presses Escape. - -Editor Registry - - The editor registry remembers a function that will be called to create - an editor for a given type. -""" - -__author__ = "Phillip Piper" -__date__ = "3 May 2008" -__version__ = "1.0" - -import datetime -import wx - -#====================================================================== -# Editor Registry - -# Module level variable -_cellEditorRegistrySingleton = None - -def CellEditorRegistry(): - """ - Return the registry that is managing type to creator functions - """ - global _cellEditorRegistrySingleton - - if _cellEditorRegistrySingleton is None: - _cellEditorRegistrySingleton = EditorRegistry() - - return _cellEditorRegistrySingleton - - -class EditorRegistry: - """ - An *EditorRegistry* manages a mapping of types onto creator functions. - - When called, creator functions will create the appropriate kind of cell editor - """ - - def __init__(self): - self.typeToFunctionMap = {} - - # Standard types and their creator functions - self.typeToFunctionMap[str] = self._MakeStringEditor - self.typeToFunctionMap[unicode] = self._MakeStringEditor - self.typeToFunctionMap[bool] = self._MakeBoolEditor - self.typeToFunctionMap[int] = self._MakeIntegerEditor - self.typeToFunctionMap[long] = self._MakeLongEditor - self.typeToFunctionMap[float] = self._MakeFloatEditor - self.typeToFunctionMap[datetime.datetime] = self._MakeDateTimeEditor - self.typeToFunctionMap[datetime.date] = self._MakeDateEditor - self.typeToFunctionMap[datetime.time] = self._MakeTimeEditor - - # TODO: Install editors for mxDateTime if installed - - def GetCreatorFunction(self, aValue): - """ - Return the creator function that is register for the type of the given value. - Return None if there is no registered function for the type. - """ - return self.typeToFunctionMap.get(type(aValue), None) - - def RegisterCreatorFunction(self, aType, aFunction): - """ - Register the given function to be called when we need an editor for the given type. - - The function must accept three parameter: an ObjectListView, row index, and subitem index. - It should return a wxWindow that is parented on the listview, and that responds to: - - - SetValue(newValue) - - - GetValue() to return the value shown in the editor - - """ - self.typeToFunctionMap[aType] = aFunction - - #---------------------------------------------------------------------------- - # Creator functions for standard types - - @staticmethod - def _MakeStringEditor(olv, rowIndex, subItemIndex): - return BaseCellTextEditor(olv, subItemIndex) - - @staticmethod - def _MakeBoolEditor(olv, rowIndex, subItemIndex): - return BooleanEditor(olv) - - @staticmethod - def _MakeIntegerEditor(olv, rowIndex, subItemIndex): - return IntEditor(olv, subItemIndex, validator=NumericValidator()) - - @staticmethod - def _MakeLongEditor(olv, rowIndex, subItemIndex): - return LongEditor(olv, subItemIndex) - - @staticmethod - def _MakeFloatEditor(olv, rowIndex, subItemIndex): - return FloatEditor(olv, subItemIndex, validator=NumericValidator("0123456789-+eE.")) - - @staticmethod - def _MakeDateTimeEditor(olv, rowIndex, subItemIndex): - dte = DateTimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - dte.formatString = column.stringConverter - - return dte - - @staticmethod - def _MakeDateEditor(olv, rowIndex, subItemIndex): - dte = DateEditor(olv, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY | wx.WANTS_CHARS) - #dte.SetValidator(MyValidator(olv)) - return dte - - @staticmethod - def _MakeTimeEditor(olv, rowIndex, subItemIndex): - editor = TimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - editor.formatString = column.stringConverter - - return editor - -#====================================================================== -# Cell editors - - -class BooleanEditor(wx.Choice): - """This is a simple editor to edit a boolean value that can be used in an - ObjectListView""" - - def __init__(self, *args, **kwargs): - kwargs["choices"] = ["True", "False"] - wx.Choice.__init__(self, *args, **kwargs) - - def GetValue(self): - "Get the value from the editor" - return self.GetSelection() == 0 - - def SetValue(self, value): - "Put a new value into the editor" - if value: - self.Select(0) - else: - self.Select(1) - -#---------------------------------------------------------------------------- - -class BaseCellTextEditor(wx.TextCtrl): - """This is a base text editor for text-like editors used in an ObjectListView""" - - def __init__(self, olv, subItemIndex, **kwargs): - style = wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB - # Allow for odd case where parent isn't an ObjectListView - if hasattr(olv, "columns"): - if olv.HasFlag(wx.LC_ICON): - style |= (wx.TE_CENTRE | wx.TE_MULTILINE) - else: - style |= olv.columns[subItemIndex].GetAlignmentForText() - wx.TextCtrl.__init__(self, olv, style=style, **kwargs) - - # With the MULTILINE flag, the text control always has a vertical - # scrollbar, which looks stupid. I don't know how to get rid of it. - # This doesn't do it: - # self.ToggleWindowStyle(wx.VSCROLL) - -#---------------------------------------------------------------------------- - -class IntEditor(BaseCellTextEditor): - """This is a text editor for integers for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return int(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, int): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class LongEditor(BaseCellTextEditor): - """This is a text editor for long values for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return long(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, long): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class FloatEditor(BaseCellTextEditor): - """This is a text editor for floats for use in an ObjectListView. - - Because of the trouble of precisely converting floats to strings, - this editor sometimes behaves a little strangely.""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return float(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, float): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class DateTimeEditor(BaseCellTextEditor): - """ - A DateTimeEditor allows the user to enter a date/time combination, where the time is optional - and many formats of date and time are allowed. - - The control accepts these date formats (in all cases, the year can be only 2 digits): - - '31/12/2008' - - '2008/12/31' - - '12/31/2008' - - '31 December 2008' - - '31 Dec 2008' - - 'Dec 31 2008' - - 'December 31 2008' - - Slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - - The control accepts these time formats: - - '23:59:59' - - '11:59:59pm' - - '23:59' - - '11:59pm' - - '11pm' - - The colons are required. The am/pm is case insensitive. - - The implementation uses a brute force approach to parsing the data. - """ - # Acceptable formats: - # '31/12/2008', '2008/12/31', '12/31/2008', '31 December 2008', '31 Dec 2008', 'Dec 31 2007' - # second line is the same but with two-digit year. - # slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - STD_DATE_FORMATS = ['%d %m %Y', '%Y %m %d', '%m %d %Y', '%d %B %Y', '%d %b %Y', '%b %d %Y', '%B %d %Y', - '%d %m %y', '%y %m %d', '%m %d %y', '%d %B %y', '%d %b %y', '%b %d %y', '%B %d %y'] - - STD_DATE_WITHOUT_YEAR_FORMATS = ['%d %m', '%m %d', '%d %B', '%d %b', '%B %d', '%b %d'] - - # Acceptable formats: '23:59:59', '11:59:59pm', '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%H:%M:%S', '%I:%M:%S %p', '%H:%M', '%I:%M %p', '%I %p'] - - # These separators are treated as whitespace - STD_SEPARATORS = "/-," - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X %x" - - self.allDateTimeFormats = [] - for dtFmt in self.STD_DATE_FORMATS: - self.allDateTimeFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeFormats.append("%s %s" % (dtFmt, timeFmt)) - - self.allDateTimeWithoutYearFormats = [] - for dtFmt in self.STD_DATE_WITHOUT_YEAR_FORMATS: - self.allDateTimeWithoutYearFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeWithoutYearFormats.append("%s %s" % (dtFmt, timeFmt)) - - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, datetime.datetime): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - return self._ParseDateTime(s) - - - def _ParseDateTime(self, s): - # Try the installed format string first - try: - return datetime.datetime.strptime(s, self.formatString) - except ValueError: - pass - - for x in self.STD_SEPARATORS: - s = s.replace(x, " ") - - # Because of the logic of strptime, we have to check shorter patterns first. - # For example: - # "31 12" matches "%d %m %y" => datetime(2012, 1, 3, 0, 0) ?? - # but we want: - # "31 12" to match "%d %m" => datetime(1900, 12, 31, 0, 0) - # JPP 4/4/2008 Python 2.5.1 - for fmt in self.allDateTimeWithoutYearFormats: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.replace(year=datetime.datetime.today().year) - except ValueError: - pass - - for fmt in self.allDateTimeFormats: - try: - return datetime.datetime.strptime(s, fmt) - except ValueError: - pass - - return None - -#---------------------------------------------------------------------------- - -class NumericValidator(wx.PyValidator): - """This validator only accepts numeric keys""" - - def __init__(self, acceptableChars="0123456789+-"): - wx.PyValidator.__init__(self) - self.Bind(wx.EVT_CHAR, self._OnChar) - self.acceptableChars = acceptableChars - self.acceptableCodes = [ord(x) for x in self.acceptableChars] - stdEditKeys = [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE, wx.WXK_CANCEL, - wx.WXK_TAB, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_HOME, wx.WXK_END, - wx.WXK_LEFT, wx.WXK_RIGHT] - self.acceptableCodes.extend(stdEditKeys) - - def Clone(self): - "Make a new copy of this validator" - return NumericValidator(self.acceptableChars) - - def _OnChar(self, event): - "Handle the OnChar event by rejecting non-numerics" - if event.GetModifiers() != 0 and event.GetModifiers() != wx.MOD_SHIFT: - event.Skip() - return - - if event.GetKeyCode() in self.acceptableCodes: - event.Skip() - return - - wx.Bell() - -#---------------------------------------------------------------------------- - -class DateEditor(wx.DatePickerCtrl): - """ - This control uses standard datetime. - wx.DatePickerCtrl works only with wx.DateTime, but they are strange beasts. - wx.DataTime use 0 indexed months, i.e. January==0 and December==11. - """ - - def __init__(self, *args, **kwargs): - wx.DatePickerCtrl.__init__(self, *args, **kwargs) - self.SetValue(None) - - def SetValue(self, value): - if value: - dt = wx.DateTime() - dt.Set(value.day, value.month-1, value.year) - else: - dt = wx.DateTime.Today() - wx.DatePickerCtrl.SetValue(self, dt) - - def GetValue(self): - "Get the value from the editor" - dt = wx.DatePickerCtrl.GetValue(self) - if dt.IsOk(): - return datetime.date(dt.Year, dt.Month+1, dt.Day) - else: - return None - -#---------------------------------------------------------------------------- - -class TimeEditor(BaseCellTextEditor): - """A text editor that expects and return time values""" - - # Acceptable formats: '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%X', '%H:%M', '%I:%M %p', '%I %p'] - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X" - - def SetValue(self, value): - "Put a new value into the editor" - value = value or "" - if isinstance(value, datetime.time): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - fmts = self.STD_TIME_FORMATS[:] - if self.formatString not in fmts: - fmts.insert(0, self.formatString) - for fmt in fmts: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.time() - except ValueError: - pass - - return None - -#====================================================================== -# Auto complete controls - -def MakeAutoCompleteTextBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a TextCtrl that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - #THINK: We could make this time based, i.e. it escapes after 1 second. - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - tb = BaseCellTextEditor(olv, columnIndex) - AutoCompleteHelper(tb, list(options)) - return tb - -def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a ComboBox that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - cb = wx.ComboBox(olv, choices=list(options), - style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) - AutoCompleteHelper(cb) - return cb - - -#------------------------------------------------------------------------- - -class AutoCompleteHelper(object): - """ - This class operates on a text control or combobox, and automatically completes the - text typed by the user from a list of entries in a given list. - - """ - - def __init__(self, control, possibleValues=None): - self.control = control - self.lastUserEnteredString = self.control.GetValue() - self.control.Bind(wx.EVT_TEXT, self._OnTextEvent) - if isinstance(self.control, wx.ComboBox): - self.possibleValues = self.control.GetStrings() - else: - self.possibleValues = possibleValues or [] - self.lowerCasePossibleValues = [x.lower() for x in self.possibleValues] - - - def _OnTextEvent(self, evt): - evt.Skip() - # After the SetValue() we want to ignore this event. If we get this event - # and the value hasn't been modified, we know it was a SetValue() call. - if hasattr(self.control, "IsModified") and not self.control.IsModified(): - return - - # If the text has changed more than the user just typing one letter, - # then don't try to autocomplete it. - if len(evt.GetString()) != len(self.lastUserEnteredString)+1: - self.lastUserEnteredString = evt.GetString() - return - - self.lastUserEnteredString = evt.GetString() - s = evt.GetString().lower() - for i, x in enumerate(self.lowerCasePossibleValues): - if x.startswith(s): - self._AutocompleteWith(self.possibleValues[i]) - break - - - def _AutocompleteWith(self, newValue): - """Suggest the given value by autocompleting it.""" - # GetInsertionPoint() doesn't seem reliable under linux - insertIndex = len(self.control.GetValue()) - self.control.SetValue(newValue) - if isinstance(self.control, wx.ComboBox): - self.control.SetMark(insertIndex, len(newValue)) - else: - # Seems that under linux, selecting only seems to work here if we do it - # outside of the text event - wx.CallAfter(self.control.SetSelection, insertIndex, len(newValue)) +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------------- +# Name: CellEditor.py +# Author: Phillip Piper +# Created: 3 April 2008 +# SVN-ID: $Id$ +# Copyright: (c) 2008 by Phillip Piper, 2008 +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/05/26 JPP Fixed pyLint annoyances +# 2008/04/04 JPP Initial version complete +#---------------------------------------------------------------------------- +# To do: +# - there has to be a better DateTimeEditor somewhere!! + +""" +The *CellEditor* module provides some editors for standard types that can be installed +in an *ObjectListView*. It also provides a *Registry* that maps between standard types +and functions that will create editors for that type. + +Cell Editors + + A cell editor can be any subclass of wx.Window provided that it supports the + following protocol: + + SetValue(self, value) + The editor should show the given value for editing + + GetValue(self) + The editor should return the value that it holds. Return None to indicate + an invalid value. The returned value should be of the correct type, i.e. + don't return a string if the editor was registered for the bool type. + + The editor should invoke FinishCellEdit() on its parent ObjectListView when it + loses focus or when the user commits the change by pressing Return or Enter. + + The editor should invoke CancelCellEdit() on its parent ObjectListView when + the user presses Escape. + +Editor Registry + + The editor registry remembers a function that will be called to create + an editor for a given type. +""" + +__author__ = "Phillip Piper" +__date__ = "3 May 2008" +__version__ = "1.0" + +import datetime +import wx + +#====================================================================== +# Editor Registry + +# Module level variable +_cellEditorRegistrySingleton = None + +def CellEditorRegistry(): + """ + Return the registry that is managing type to creator functions + """ + global _cellEditorRegistrySingleton + + if _cellEditorRegistrySingleton is None: + _cellEditorRegistrySingleton = EditorRegistry() + + return _cellEditorRegistrySingleton + + +class EditorRegistry: + """ + An *EditorRegistry* manages a mapping of types onto creator functions. + + When called, creator functions will create the appropriate kind of cell editor + """ + + def __init__(self): + self.typeToFunctionMap = {} + + # Standard types and their creator functions + self.typeToFunctionMap[str] = self._MakeStringEditor + self.typeToFunctionMap[unicode] = self._MakeStringEditor + self.typeToFunctionMap[bool] = self._MakeBoolEditor + self.typeToFunctionMap[int] = self._MakeIntegerEditor + self.typeToFunctionMap[long] = self._MakeLongEditor + self.typeToFunctionMap[float] = self._MakeFloatEditor + self.typeToFunctionMap[datetime.datetime] = self._MakeDateTimeEditor + self.typeToFunctionMap[datetime.date] = self._MakeDateEditor + self.typeToFunctionMap[datetime.time] = self._MakeTimeEditor + + # TODO: Install editors for mxDateTime if installed + + def GetCreatorFunction(self, aValue): + """ + Return the creator function that is register for the type of the given value. + Return None if there is no registered function for the type. + """ + return self.typeToFunctionMap.get(type(aValue), None) + + def RegisterCreatorFunction(self, aType, aFunction): + """ + Register the given function to be called when we need an editor for the given type. + + The function must accept three parameter: an ObjectListView, row index, and subitem index. + It should return a wxWindow that is parented on the listview, and that responds to: + + - SetValue(newValue) + + - GetValue() to return the value shown in the editor + + """ + self.typeToFunctionMap[aType] = aFunction + + #---------------------------------------------------------------------------- + # Creator functions for standard types + + @staticmethod + def _MakeStringEditor(olv, rowIndex, subItemIndex): + return BaseCellTextEditor(olv, subItemIndex) + + @staticmethod + def _MakeBoolEditor(olv, rowIndex, subItemIndex): + return BooleanEditor(olv) + + @staticmethod + def _MakeIntegerEditor(olv, rowIndex, subItemIndex): + return IntEditor(olv, subItemIndex, validator=NumericValidator()) + + @staticmethod + def _MakeLongEditor(olv, rowIndex, subItemIndex): + return LongEditor(olv, subItemIndex) + + @staticmethod + def _MakeFloatEditor(olv, rowIndex, subItemIndex): + return FloatEditor(olv, subItemIndex, validator=NumericValidator("0123456789-+eE.")) + + @staticmethod + def _MakeDateTimeEditor(olv, rowIndex, subItemIndex): + dte = DateTimeEditor(olv, subItemIndex) + + column = olv.columns[subItemIndex] + if isinstance(column.stringConverter, basestring): + dte.formatString = column.stringConverter + + return dte + + @staticmethod + def _MakeDateEditor(olv, rowIndex, subItemIndex): + dte = DateEditor(olv, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY | wx.WANTS_CHARS) + #dte.SetValidator(MyValidator(olv)) + return dte + + @staticmethod + def _MakeTimeEditor(olv, rowIndex, subItemIndex): + editor = TimeEditor(olv, subItemIndex) + + column = olv.columns[subItemIndex] + if isinstance(column.stringConverter, basestring): + editor.formatString = column.stringConverter + + return editor + +#====================================================================== +# Cell editors + + +class BooleanEditor(wx.Choice): + """This is a simple editor to edit a boolean value that can be used in an + ObjectListView""" + + def __init__(self, *args, **kwargs): + kwargs["choices"] = ["True", "False"] + wx.Choice.__init__(self, *args, **kwargs) + + def GetValue(self): + "Get the value from the editor" + return self.GetSelection() == 0 + + def SetValue(self, value): + "Put a new value into the editor" + if value: + self.Select(0) + else: + self.Select(1) + +#---------------------------------------------------------------------------- + +class BaseCellTextEditor(wx.TextCtrl): + """This is a base text editor for text-like editors used in an ObjectListView""" + + def __init__(self, olv, subItemIndex, **kwargs): + style = wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB + # Allow for odd case where parent isn't an ObjectListView + if hasattr(olv, "columns"): + if olv.HasFlag(wx.LC_ICON): + style |= (wx.TE_CENTRE | wx.TE_MULTILINE) + else: + style |= olv.columns[subItemIndex].GetAlignmentForText() + wx.TextCtrl.__init__(self, olv, style=style, **kwargs) + + # With the MULTILINE flag, the text control always has a vertical + # scrollbar, which looks stupid. I don't know how to get rid of it. + # This doesn't do it: + # self.ToggleWindowStyle(wx.VSCROLL) + +#---------------------------------------------------------------------------- + +class IntEditor(BaseCellTextEditor): + """This is a text editor for integers for use in an ObjectListView""" + + def GetValue(self): + "Get the value from the editor" + s = wx.TextCtrl.GetValue(self).strip() + try: + return int(s) + except ValueError: + return None + + def SetValue(self, value): + "Put a new value into the editor" + if isinstance(value, int): + value = repr(value) + wx.TextCtrl.SetValue(self, value) + +#---------------------------------------------------------------------------- + +class LongEditor(BaseCellTextEditor): + """This is a text editor for long values for use in an ObjectListView""" + + def GetValue(self): + "Get the value from the editor" + s = wx.TextCtrl.GetValue(self).strip() + try: + return long(s) + except ValueError: + return None + + def SetValue(self, value): + "Put a new value into the editor" + if isinstance(value, long): + value = repr(value) + wx.TextCtrl.SetValue(self, value) + +#---------------------------------------------------------------------------- + +class FloatEditor(BaseCellTextEditor): + """This is a text editor for floats for use in an ObjectListView. + + Because of the trouble of precisely converting floats to strings, + this editor sometimes behaves a little strangely.""" + + def GetValue(self): + "Get the value from the editor" + s = wx.TextCtrl.GetValue(self).strip() + try: + return float(s) + except ValueError: + return None + + def SetValue(self, value): + "Put a new value into the editor" + if isinstance(value, float): + value = repr(value) + wx.TextCtrl.SetValue(self, value) + +#---------------------------------------------------------------------------- + +class DateTimeEditor(BaseCellTextEditor): + """ + A DateTimeEditor allows the user to enter a date/time combination, where the time is optional + and many formats of date and time are allowed. + + The control accepts these date formats (in all cases, the year can be only 2 digits): + - '31/12/2008' + - '2008/12/31' + - '12/31/2008' + - '31 December 2008' + - '31 Dec 2008' + - 'Dec 31 2008' + - 'December 31 2008' + + Slash character can also be '-' or ' '. Consecutive whitespace are collapsed. + + The control accepts these time formats: + - '23:59:59' + - '11:59:59pm' + - '23:59' + - '11:59pm' + - '11pm' + + The colons are required. The am/pm is case insensitive. + + The implementation uses a brute force approach to parsing the data. + """ + # Acceptable formats: + # '31/12/2008', '2008/12/31', '12/31/2008', '31 December 2008', '31 Dec 2008', 'Dec 31 2007' + # second line is the same but with two-digit year. + # slash character can also be '-' or ' '. Consecutive whitespace are collapsed. + STD_DATE_FORMATS = ['%d %m %Y', '%Y %m %d', '%m %d %Y', '%d %B %Y', '%d %b %Y', '%b %d %Y', '%B %d %Y', + '%d %m %y', '%y %m %d', '%m %d %y', '%d %B %y', '%d %b %y', '%b %d %y', '%B %d %y'] + + STD_DATE_WITHOUT_YEAR_FORMATS = ['%d %m', '%m %d', '%d %B', '%d %b', '%B %d', '%b %d'] + + # Acceptable formats: '23:59:59', '11:59:59pm', '23:59', '11:59pm', '11pm' + STD_TIME_FORMATS = ['%H:%M:%S', '%I:%M:%S %p', '%H:%M', '%I:%M %p', '%I %p'] + + # These separators are treated as whitespace + STD_SEPARATORS = "/-," + + def __init__(self, *args, **kwargs): + BaseCellTextEditor.__init__(self, *args, **kwargs) + self.formatString = "%X %x" + + self.allDateTimeFormats = [] + for dtFmt in self.STD_DATE_FORMATS: + self.allDateTimeFormats.append(dtFmt) + for timeFmt in self.STD_TIME_FORMATS: + self.allDateTimeFormats.append("%s %s" % (dtFmt, timeFmt)) + + self.allDateTimeWithoutYearFormats = [] + for dtFmt in self.STD_DATE_WITHOUT_YEAR_FORMATS: + self.allDateTimeWithoutYearFormats.append(dtFmt) + for timeFmt in self.STD_TIME_FORMATS: + self.allDateTimeWithoutYearFormats.append("%s %s" % (dtFmt, timeFmt)) + + + def SetValue(self, value): + "Put a new value into the editor" + if isinstance(value, datetime.datetime): + value = value.strftime(self.formatString) + wx.TextCtrl.SetValue(self, value) + + + def GetValue(self): + "Get the value from the editor" + s = wx.TextCtrl.GetValue(self).strip() + return self._ParseDateTime(s) + + + def _ParseDateTime(self, s): + # Try the installed format string first + try: + return datetime.datetime.strptime(s, self.formatString) + except ValueError: + pass + + for x in self.STD_SEPARATORS: + s = s.replace(x, " ") + + # Because of the logic of strptime, we have to check shorter patterns first. + # For example: + # "31 12" matches "%d %m %y" => datetime(2012, 1, 3, 0, 0) ?? + # but we want: + # "31 12" to match "%d %m" => datetime(1900, 12, 31, 0, 0) + # JPP 4/4/2008 Python 2.5.1 + for fmt in self.allDateTimeWithoutYearFormats: + try: + dt = datetime.datetime.strptime(s, fmt) + return dt.replace(year=datetime.datetime.today().year) + except ValueError: + pass + + for fmt in self.allDateTimeFormats: + try: + return datetime.datetime.strptime(s, fmt) + except ValueError: + pass + + return None + +#---------------------------------------------------------------------------- + +class NumericValidator(wx.PyValidator): + """This validator only accepts numeric keys""" + + def __init__(self, acceptableChars="0123456789+-"): + wx.PyValidator.__init__(self) + self.Bind(wx.EVT_CHAR, self._OnChar) + self.acceptableChars = acceptableChars + self.acceptableCodes = [ord(x) for x in self.acceptableChars] + stdEditKeys = [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE, wx.WXK_CANCEL, + wx.WXK_TAB, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_HOME, wx.WXK_END, + wx.WXK_LEFT, wx.WXK_RIGHT] + self.acceptableCodes.extend(stdEditKeys) + + def Clone(self): + "Make a new copy of this validator" + return NumericValidator(self.acceptableChars) + + def _OnChar(self, event): + "Handle the OnChar event by rejecting non-numerics" + if event.GetModifiers() != 0 and event.GetModifiers() != wx.MOD_SHIFT: + event.Skip() + return + + if event.GetKeyCode() in self.acceptableCodes: + event.Skip() + return + + wx.Bell() + +#---------------------------------------------------------------------------- + +class DateEditor(wx.DatePickerCtrl): + """ + This control uses standard datetime. + wx.DatePickerCtrl works only with wx.DateTime, but they are strange beasts. + wx.DataTime use 0 indexed months, i.e. January==0 and December==11. + """ + + def __init__(self, *args, **kwargs): + wx.DatePickerCtrl.__init__(self, *args, **kwargs) + self.SetValue(None) + + def SetValue(self, value): + if value: + dt = wx.DateTime() + dt.Set(value.day, value.month-1, value.year) + else: + dt = wx.DateTime.Today() + wx.DatePickerCtrl.SetValue(self, dt) + + def GetValue(self): + "Get the value from the editor" + dt = wx.DatePickerCtrl.GetValue(self) + if dt.IsOk(): + return datetime.date(dt.Year, dt.Month+1, dt.Day) + else: + return None + +#---------------------------------------------------------------------------- + +class TimeEditor(BaseCellTextEditor): + """A text editor that expects and return time values""" + + # Acceptable formats: '23:59', '11:59pm', '11pm' + STD_TIME_FORMATS = ['%X', '%H:%M', '%I:%M %p', '%I %p'] + + def __init__(self, *args, **kwargs): + BaseCellTextEditor.__init__(self, *args, **kwargs) + self.formatString = "%X" + + def SetValue(self, value): + "Put a new value into the editor" + value = value or "" + if isinstance(value, datetime.time): + value = value.strftime(self.formatString) + wx.TextCtrl.SetValue(self, value) + + def GetValue(self): + "Get the value from the editor" + s = wx.TextCtrl.GetValue(self).strip() + fmts = self.STD_TIME_FORMATS[:] + if self.formatString not in fmts: + fmts.insert(0, self.formatString) + for fmt in fmts: + try: + dt = datetime.datetime.strptime(s, fmt) + return dt.time() + except ValueError: + pass + + return None + +#====================================================================== +# Auto complete controls + +def MakeAutoCompleteTextBox(olv, columnIndex, maxObjectsToConsider=10000): + """ + Return a TextCtrl that lets the user choose from all existing values in this column. + Do not call for large lists + """ + col = olv.columns[columnIndex] + #THINK: We could make this time based, i.e. it escapes after 1 second. + maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) + options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) + tb = BaseCellTextEditor(olv, columnIndex) + AutoCompleteHelper(tb, list(options)) + return tb + +def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): + """ + Return a ComboBox that lets the user choose from all existing values in this column. + Do not call for large lists + """ + col = olv.columns[columnIndex] + maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) + options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) + cb = wx.ComboBox(olv, choices=list(options), + style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) + AutoCompleteHelper(cb) + return cb + + +#------------------------------------------------------------------------- + +class AutoCompleteHelper(object): + """ + This class operates on a text control or combobox, and automatically completes the + text typed by the user from a list of entries in a given list. + + """ + + def __init__(self, control, possibleValues=None): + self.control = control + self.lastUserEnteredString = self.control.GetValue() + self.control.Bind(wx.EVT_TEXT, self._OnTextEvent) + if isinstance(self.control, wx.ComboBox): + self.possibleValues = self.control.GetStrings() + else: + self.possibleValues = possibleValues or [] + self.lowerCasePossibleValues = [x.lower() for x in self.possibleValues] + + + def _OnTextEvent(self, evt): + evt.Skip() + # After the SetValue() we want to ignore this event. If we get this event + # and the value hasn't been modified, we know it was a SetValue() call. + if hasattr(self.control, "IsModified") and not self.control.IsModified(): + return + + # If the text has changed more than the user just typing one letter, + # then don't try to autocomplete it. + if len(evt.GetString()) != len(self.lastUserEnteredString)+1: + self.lastUserEnteredString = evt.GetString() + return + + self.lastUserEnteredString = evt.GetString() + s = evt.GetString().lower() + for i, x in enumerate(self.lowerCasePossibleValues): + if x.startswith(s): + self._AutocompleteWith(self.possibleValues[i]) + break + + + def _AutocompleteWith(self, newValue): + """Suggest the given value by autocompleting it.""" + # GetInsertionPoint() doesn't seem reliable under linux + insertIndex = len(self.control.GetValue()) + self.control.SetValue(newValue) + if isinstance(self.control, wx.ComboBox): + self.control.SetMark(insertIndex, len(newValue)) + else: + # Seems that under linux, selecting only seems to work here if we do it + # outside of the text event + wx.CallAfter(self.control.SetSelection, insertIndex, len(newValue)) diff --git a/odmtools/lib/oldOlv/Filter.py b/odmtools/lib/oldOlv/Filter.py index 97dcf5d..4e2a653 100644 --- a/odmtools/lib/oldOlv/Filter.py +++ b/odmtools/lib/oldOlv/Filter.py @@ -1,138 +1,138 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: Filter.py -# Author: Phillip Piper -# Created: 26 August 2008 -# Copyright: (c) 2008 Phillip Piper -# SVN-ID: $Id$ -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/26 JPP First version -#---------------------------------------------------------------------------- -# To do: -# - -""" -Filters provide a structured mechanism to display only some of the model objects -given to an ObjectListView. Only those model objects which are 'chosen' by -an installed filter will be presented to the user. - -Filters are simple callable objects which accept a single parameter, which -is the list of model objects to be filtered, and returns a collection of -those objects which will be presented to the user. - -This module provides some standard filters. - -Filters almost always impose a performance penalty on the ObjectListView. -The penalty is normally O(n) since the filter normally examines each model -object to see if it should be included. Head() and Tail() are exceptions -to this observation. -""" - -def Predicate(predicate): - """ - Display only those objects that match the given predicate - - Example:: - self.olv.SetFilter(Filter.Predicate(lambda x: x.IsOverdue())) - """ - return lambda modelObjects: [x for x in modelObjects if predicate(x)] - - -def Head(num): - """ - Display at most the first N of the model objects - - Example:: - self.olv.SetFilter(Filter.Head(1000)) - """ - return lambda modelObjects: modelObjects[:num] - - -def Tail(num): - """ - Display at most the last N of the model objects - - Example:: - self.olv.SetFilter(Filter.Tail(1000)) - """ - return lambda modelObjects: modelObjects[-num:] - - -class TextSearch(object): - """ - Return only model objects that match a given string. If columns is not empty, - only those columns will be considered when searching for the string. Otherwise, - all columns will be searched. - - Example:: - self.olv.SetFilter(Filter.TextSearch(self.olv, text="findthis")) - self.olv.RepopulateList() - """ - - def __init__(self, objectListView, columns=(), text=""): - """ - Create a filter that includes on modelObject that have 'self.text' somewhere in the given columns. - """ - self.objectListView = objectListView - self.columns = columns - self.text = text - - def __call__(self, modelObjects): - """ - Return the model objects that contain our text in one of the columns to consider - """ - if not self.text: - return modelObjects - - # In non-report views, we can only search the primary column - if self.objectListView.InReportView(): - cols = self.columns or self.objectListView.columns - else: - cols = [self.objectListView.columns[0]] - - textToFind = self.text.lower() - - def _containsText(modelObject): - for col in cols: - if textToFind in col.GetStringValue(modelObject).lower(): - return True - return False - - return [x for x in modelObjects if _containsText(x)] - - def SetText(self, text): - """ - Set the text that this filter will match. Set this to None or "" to disable the filter. - """ - self.text = text - - -class Chain(object): - """ - Return only model objects that match all of the given filters. - - Example:: - # Show at most 100 people whose salary is over 50,000 - salaryFilter = Filter.Predicate(lambda person: person.GetSalary() > 50000) - self.olv.SetFilter(Filter.Chain(salaryFilter, Filter.Tail(100))) - self.olv.RepopulateList() - """ - - def __init__(self, *filters): - """ - Create a filter that performs all the given filters. - - The order of the filters is important. - """ - self.filters = filters - - - def __call__(self, modelObjects): - """ - Return the model objects that match all of our filters - """ - for filter in self.filters: - modelObjects = filter(modelObjects) - return modelObjects +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------------- +# Name: Filter.py +# Author: Phillip Piper +# Created: 26 August 2008 +# Copyright: (c) 2008 Phillip Piper +# SVN-ID: $Id$ +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/08/26 JPP First version +#---------------------------------------------------------------------------- +# To do: +# + +""" +Filters provide a structured mechanism to display only some of the model objects +given to an ObjectListView. Only those model objects which are 'chosen' by +an installed filter will be presented to the user. + +Filters are simple callable objects which accept a single parameter, which +is the list of model objects to be filtered, and returns a collection of +those objects which will be presented to the user. + +This module provides some standard filters. + +Filters almost always impose a performance penalty on the ObjectListView. +The penalty is normally O(n) since the filter normally examines each model +object to see if it should be included. Head() and Tail() are exceptions +to this observation. +""" + +def Predicate(predicate): + """ + Display only those objects that match the given predicate + + Example:: + self.olv.SetFilter(Filter.Predicate(lambda x: x.IsOverdue())) + """ + return lambda modelObjects: [x for x in modelObjects if predicate(x)] + + +def Head(num): + """ + Display at most the first N of the model objects + + Example:: + self.olv.SetFilter(Filter.Head(1000)) + """ + return lambda modelObjects: modelObjects[:num] + + +def Tail(num): + """ + Display at most the last N of the model objects + + Example:: + self.olv.SetFilter(Filter.Tail(1000)) + """ + return lambda modelObjects: modelObjects[-num:] + + +class TextSearch(object): + """ + Return only model objects that match a given string. If columns is not empty, + only those columns will be considered when searching for the string. Otherwise, + all columns will be searched. + + Example:: + self.olv.SetFilter(Filter.TextSearch(self.olv, text="findthis")) + self.olv.RepopulateList() + """ + + def __init__(self, objectListView, columns=(), text=""): + """ + Create a filter that includes on modelObject that have 'self.text' somewhere in the given columns. + """ + self.objectListView = objectListView + self.columns = columns + self.text = text + + def __call__(self, modelObjects): + """ + Return the model objects that contain our text in one of the columns to consider + """ + if not self.text: + return modelObjects + + # In non-report views, we can only search the primary column + if self.objectListView.InReportView(): + cols = self.columns or self.objectListView.columns + else: + cols = [self.objectListView.columns[0]] + + textToFind = self.text.lower() + + def _containsText(modelObject): + for col in cols: + if textToFind in col.GetStringValue(modelObject).lower(): + return True + return False + + return [x for x in modelObjects if _containsText(x)] + + def SetText(self, text): + """ + Set the text that this filter will match. Set this to None or "" to disable the filter. + """ + self.text = text + + +class Chain(object): + """ + Return only model objects that match all of the given filters. + + Example:: + # Show at most 100 people whose salary is over 50,000 + salaryFilter = Filter.Predicate(lambda person: person.GetSalary() > 50000) + self.olv.SetFilter(Filter.Chain(salaryFilter, Filter.Tail(100))) + self.olv.RepopulateList() + """ + + def __init__(self, *filters): + """ + Create a filter that performs all the given filters. + + The order of the filters is important. + """ + self.filters = filters + + + def __call__(self, modelObjects): + """ + Return the model objects that match all of our filters + """ + for filter in self.filters: + modelObjects = filter(modelObjects) + return modelObjects diff --git a/odmtools/lib/oldOlv/ListCtrlPrinter.py b/odmtools/lib/oldOlv/ListCtrlPrinter.py index 550ec6b..e1b3fe5 100644 --- a/odmtools/lib/oldOlv/ListCtrlPrinter.py +++ b/odmtools/lib/oldOlv/ListCtrlPrinter.py @@ -1,2797 +1,2797 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Inspired beginning -#---------------------------------------------------------------------------- -# To do: -# - persistence of ReportFormat -# - allow cell contents to be vertically aligned -# - in light of several of the "to dos", consider having CellBlockFormat object -# - write a data abstraction layer between the printer and the ListCtrl. -# This layer could understand ObjectListViews and be more efficient. -# - consider if this could be made to work with a wx.Grid (needs data abstraction layer) - -# Known issues: -# - the 'space' setting on decorations is not intuitive - - -""" -An ``ListCtrlPrinter`` takes an ``ObjectListView`` or ``wx.ListCtrl`` and turns it into a -pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -This will produce a report with reasonable formatting. The formatting of a report is -controlled completely by the ReportFormat object of the ListCtrlPrinter. To change the appearance -of the report, you change the settings in this object. - -A report consists of various sections (called "blocks") and each of these blocks has a -matching BlockFormat object in the ReportFormat. So, to modify the format of the -page header, you change the ReportFormat.PageHeader object. - -A ReportFormat object has the following properties which control the appearance of the matching -sections of the report: - -* PageHeader -* ListHeader -* ColumnHeader -* GroupTitle -* Row -* ListFooter -* PageFooter -* Page - -These properties return BlockFormat objects, which have the following properties: - -* AlwaysCenter -* CanWrap -* Font -* Padding -* TextAlignment -* TextColor - -* CellPadding -* GridPen - -Implementation -============== - -A ``ListCtrlPrinter`` is a *Facade* over two classes: - - ``ListCtrlPrintout``, which handles the interface to the wx printing subsystem - - ``ReportEngine``, which does the actual work of creating the report - -The ``ListCtrlPrintout`` handles all the details of the wx printing subsystem. In -particular, it configures the printing DC so that its origin and scale are correct. This -enables the ``ReportEngine`` to simply render the report without knowing the -characteristics of the underlying printer DPI, unprintable region, or the scale of a print -preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like -actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards -to the ``ReportEngine``). - -The ``ReportEngine`` uses a block structure approach to reports. Each element of -a report is a "block", which stacks vertically with other blocks. - -When a block is printed, it can either render itself into a given DC or it can replace -itself in the report structure with one or more other blocks. A ``TextBlock`` renders -itself by drawing into the DC. In contrast, the block that prints a ``ListCtrl`` replaces -itself with a ``ListHeaderBlock``, a ``ColumnHeaderBlock``, ``ListRowsBlock`` and finally a -``ListFooterBlock``. - -The blocks describe the structure of the report. The formatting of a report is -controlled by ``BlockFormat`` objects. The ``ReportFormat`` contains a ``BlockFormat`` -object for each formattable section of the report. - -Each section must be formatted in same fashion, i.e. all column headers will look the same, -all page footer will look the same. There is no way to make the first footer look one way, -and the second look a different way. -""" - -import datetime -import math - -import wx - -from WordWrapRenderer import WordWrapRenderer - -#---------------------------------------------------------------------------- - -class ListCtrlPrinter(object): - """ - An ListCtrlPrinter creates a pretty report from an ObjectListView/ListCtrl. - - """ - - def __init__(self, listCtrl=None, title="ListCtrl Printing"): - """ - """ - self.printout = ListCtrlPrintout(self) - self.engine = ReportEngine() - if listCtrl is not None: - self.AddListCtrl(listCtrl, title) - - #---------------------------------------------------------------------------- - # Accessing - - def GetPageFooter(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page footer - """ - return self.engine.pageFooter - - def SetPageFooter(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page footer. - - leftText can be a string or a 3-tuple of strings. - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageFooter = leftText - else: - self.engine.pageFooter = (leftText, centerText, rightText) - - def GetPageHeader(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page header - """ - return self.engine.pageHeader - - def SetPageHeader(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page header - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageHeader = leftText - else: - self.engine.pageHeader = (leftText, centerText, rightText) - - def GetPrintData(self): - """ - Return the wx.PrintData that controls the printing of this report - """ - return self.printout.printData - - def GetReportFormat(self): - """ - Return the ReportFormat object that controls the appearance of this printout - """ - return self.engine.reportFormat - - def SetReportFormat(self, fmt): - """ - Set the ReportFormat object that controls the appearance of this printout - """ - self.engine.reportFormat = fmt - - def GetWatermark(self, txt): - """ - Get the text that will be printed as a watermark on the report - """ - return self.engine.watermark - - def SetWatermark(self, txt): - """ - Set the text that will be printed as a watermark on the report - """ - self.engine.watermark = txt - - ReportFormat = property(GetReportFormat, SetReportFormat) - PageFooter = property(GetPageFooter, SetPageFooter) - PageHeader = property(GetPageHeader, SetPageHeader) - PrintData = property(GetPrintData) - Watermark = property(GetWatermark, SetWatermark) - - #---------------------------------------------------------------------------- - # Setup - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - self.engine.AddListCtrl(listCtrl, title) - - - def Clear(self): - """ - Remove all ListCtrls from this printer - """ - self.engine.ClearListCtrls() - - #---------------------------------------------------------------------------- - # Printing Commands - - def PageSetup(self, parent=None): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - self.printout.PageSetup(parent) - - - def PrintPreview(self, parent=None, title="ListCtrl Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - self.printout.PrintPreview(parent, title, bounds) - - - def Print(self, parent=None): - """ - Print this report to the selected printer - """ - self.printout.DoPrint(parent) - - #---------------------------------------------------------------------------- - # Callbacks - # These methods are invoked by the ListCtrlPrintout when required - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - return self.engine.CalculateTotalPages(dc, bounds) - - - def StartPrinting(self): - """ - A new print job is about to begin. - """ - self.engine.StartPrinting() - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - self.engine.PrintPage(dc, pageNumber, bounds) - -#---------------------------------------------------------------------------- - -class ReportEngine(object): - """ - A ReportEngine handles all the work of actually producing a report. - - Public instance variables (all others should be treated as private): - - * dateFormat - When the current date/time is substituted into report text, how should - the datetime be formatted? This must be a valid format string for the - strftime() method. - Default: "%x %X" - - """ - - def __init__(self): - """ - """ - self.currentPage = -1 - self.totalPages = -1 - self.blocks = list() - self.blockInsertionIndex = 0 - self.listCtrls = list() - self.dateFormat = "%x %X" - - self.reportFormat = ReportFormat.Normal() - - self.watermark = "" - self.pageHeader = list() - self.pageFooter = list() - #self.isPrintSelectionOnly = False # not currently implemented - - # If this is False, no drawing should be done. The engine is either counting - # pages, or skipping to a specific page - self.shouldDrawBlocks = True - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the given format - """ - return self.reportFormat.GetNamedFormat(name) - - - def GetTotalPages(self): - """ - Return the total number of pages that this report will produce. - - CalculateTotalPages() must be called before this is accurate. - """ - return self.totalPages - - - def GetSubstitutionInfo(self): - """ - Return a dictionary that can be used for substituting values into strings - """ - dateString = datetime.datetime.now().strftime(self.dateFormat) - info = { - "currentPage": self.currentPage, - "date": dateString, - "totalPages": self.totalPages, - } - return info - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - self.StartPrinting() - self.totalPages = 1 - self.shouldDrawBlocks = False - while self.PrintOnePage(dc, self.totalPages, bounds): - self.totalPages += 1 - self.shouldDrawBlocks = True - return self.totalPages - - #---------------------------------------------------------------------------- - # Commands - - def AddBlock(self, block): - """ - Add the given block at the current insertion point - """ - self.blocks.insert(self.blockInsertionIndex, block) - self.blockInsertionIndex += 1 - block.engine = self - - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - if listCtrl.InReportView(): - self.listCtrls.append([listCtrl, title]) - - - def ClearListCtrls(self): - """ - Remove all ListCtrls from this report. - """ - self.listCtrls = list() - - - def DropCurrentBlock(self): - """ - Remove the current block from our list of blocks - """ - self.blocks.pop(0) - self.blockInsertionIndex = 1 - - #---------------------------------------------------------------------------- - # Printing - - def StartPrinting(self): - """ - Initial a print job on this engine - """ - self.currentPage = 0 - self.blockInsertionIndex = 0 - self.blocks = list() - self.AddBlock(ReportBlock()) - self.runningBlocks = list() - self.AddRunningBlock(PageHeaderBlock()) - self.AddRunningBlock(PageFooterBlock()) - - if self.watermark: - self._CreateReplaceWatermarkDecoration() - - - def AddRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.append(block) - block.engine = self - - - def RemoveRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.remove(block) - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - # If the request page isn't next in order, we have to restart - # the printing process and advance until we reach the desired page. - if pageNumber != self.currentPage + 1: - self.StartPrinting() - self.shouldDrawBlocks = False - for i in range(1, pageNumber): - self.PrintOnePage(dc, i, bounds) - self.shouldDrawBlocks = True - - return self.PrintOnePage(dc, pageNumber, bounds) - - - def PrintOnePage(self, dc, pageNumber, bounds): - """ - Print the current page on the given device context. - - Return true if there is still more to print. - """ - # Initialize state - self.currentPage = pageNumber - self.pageBounds = list(bounds) - self.workBounds = list(self.pageBounds) - self.SubtractDecorations(dc) - - # Print page adornments, including under-text decorations - self.DrawPageDecorations(dc, False) - for x in self.runningBlocks: - x.Print(dc) - - # Print blocks until they won't fit or we run out of blocks - while len(self.blocks) and self.blocks[0].Print(dc): - self.DropCurrentBlock() - - # Finally, print over-the-text decorations - self.DrawPageDecorations(dc, True) - - return len(self.blocks) > 0 - - - def SubtractDecorations(self, dc): - """ - # Subtract the area used from the work area - """ - fmt = self.GetNamedFormat("Page") - self.workBounds = fmt.SubtractDecorations(dc, self.workBounds) - - - def DrawPageDecorations(self, dc, over): - """ - Draw the page decorations - """ - if not self.shouldDrawBlocks: - return - - fmt = self.GetNamedFormat("Page") - bounds = list(self.pageBounds) - fmt.DrawDecorations(dc, bounds, self, over) - - - def _CreateReplaceWatermarkDecoration(self): - """ - Create a watermark decoration, replacing any existing watermark - """ - pageFmt = self.GetNamedFormat("Page") - pageFmt.decorations = [x for x in pageFmt.decorations if not isinstance(x, WatermarkDecoration)] - - watermarkFmt = self.GetNamedFormat("Watermark") - pageFmt.Add(WatermarkDecoration(self.watermark, font=watermarkFmt.Font, - color=watermarkFmt.TextColor, angle=watermarkFmt.Angle, - over=watermarkFmt.Over)) - -#---------------------------------------------------------------------------- - -class ListCtrlPrintout(wx.Printout): - """ - An ListCtrlPrintout is the interface between the wx printing system - and ListCtrlPrinter. - """ - - def __init__(self, olvPrinter, margins=None): - """ - """ - wx.Printout.__init__(self) - self.olvPrinter = olvPrinter - self.margins = margins or (wx.Point(15, 15), wx.Point(15, 15)) - self.totalPages = -1 - - self.printData = wx.PrintData() - self.printData.SetPrinterName("") # Use default printer - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - """ - Return true if this printout has the given page number - """ - return page <= self.totalPages - - - def GetPageInfo(self): - """ - Return a 4-tuple indicating the ... - """ - return (1, self.totalPages, 1, 1) - - - def GetPrintPreview(self): - """ - Get a wxPrintPreview of this report - """ - data = wx.PrintDialogData(self.printData) - forViewing = ListCtrlPrintout(self.olvPrinter, self.margins) - forPrinter = ListCtrlPrintout(self.olvPrinter, self.margins) - preview = wx.PrintPreview(forViewing, forPrinter, data) - return preview - - #---------------------------------------------------------------------------- - # Commands - - def PageSetup(self, parent): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(parent, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), data.GetMarginBottomRight()) - dlg.Destroy() - - - - def PrintPreview(self, parent, title, bounds): - """ - Show a Print Preview of this report - """ - self.preview = self.GetPrintPreview() - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent): - """ - Send the report to the configured printer - """ - try: - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - finally: - pdd.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - dc = self.GetDC() - self.SetScaleAndBounds(dc) - self.totalPages = self.olvPrinter.CalculateTotalPages(dc, self.bounds) - self.olvPrinter.StartPrinting() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - return super(ListCtrlPrintout, self).OnBeginDocument(start, end) - - def OnEndDocument(self): - super(ListCtrlPrintout, self).OnEndDocument() - - def OnBeginPrinting(self): - super(ListCtrlPrintout, self).OnBeginPrinting() - - def OnEndPrinting(self): - super(ListCtrlPrintout, self).OnEndPrinting() - - def OnPrintPage(self, page): - """ - Do the work of printing the given page number. - """ - # We bounce this back to the printer facade - dc = self.GetDC() - self.SetScaleAndBounds(dc) - return self.olvPrinter.PrintPage(dc, page, self.bounds) - - def SetScaleAndBounds(self, dc): - """ - Calculate the scale required for our printout to match what appears on screen, - and the bounds that will be effective at that scale and margins - """ - # This code comes from Robin Dunn's "wxPython In Action." - # Without that, I would never have figured this out. - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logicalScale = float(ppiPrinterX) / float(ppiScreenX) - pw, ph = self.GetPageSizePixels() - dw, dh = dc.GetSize() - scale = logicalScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - - # Now calculate our boundries - logicalUnitsMM = float(ppiPrinterX) / (logicalScale*25.4) - topLeft, bottomRight = self.margins - left = round(topLeft.x * logicalUnitsMM) - top = round(topLeft.y * logicalUnitsMM) - right = round(dc.DeviceToLogicalYRel(dw) - bottomRight.x * logicalUnitsMM) - bottom = round(dc.DeviceToLogicalYRel(dh) - bottomRight.y * logicalUnitsMM) - self.bounds = (left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ReportFormat(object): - """ - A ReportFormat defines completely how a report is formatted. - - It holds a collection of BlockFormat objects which control the - formatting of individual blocks of the report - - Public instance variables: - - * IncludeImages - Should images from the ListCtrl be printed in the report? - Default: *True* - - * IsColumnHeadingsOnEachPage - Will the column headers be printed at the top of each page? - Default: *True* - - * IsShrinkToFit - Will the columns be shrunk so they all fit into the width of one page? - Default: *False* - - * UseListCtrlTextFormat - If this is True, the text format (i.e. font and text color) of each row will be taken from the ListCtrl, - rather than from the *Cell* format. - Default: *False* - - """ - - def __init__(self): - """ - """ - # Initialize the formats that control the various portions of the report - self.Page = BlockFormat() - self.PageHeader = BlockFormat() - self.ListHeader = BlockFormat() - self.GroupTitle = BlockFormat() - self.ColumnHeader = BlockFormat() - self.Row = BlockFormat() - self.ListFooter = BlockFormat() - self.PageFooter = BlockFormat() - self.Watermark = BlockFormat() - - self.IncludeImages = True - self.IsColumnHeadingsOnEachPage = False - self.IsShrinkToFit = False - self.UseListCtrlTextFormat = True - - # Initialize the watermark format to default values - self.WatermarkFormat() - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the format used in to format a block with the given name. - """ - return getattr(self, name) - - #---------------------------------------------------------------------------- - # Commands - - def WatermarkFormat(self, font=None, color=None, angle=30, over=False): - """ - Change the format of the watermark printed on this report. - - The actual text of the water mark is set by `ListCtrlPrinter.Watermark` property. - """ - defaultFaceName = "Stencil" - self.Watermark.Font = font or wx.FFont(96, wx.FONTFAMILY_DEFAULT, 0, defaultFaceName) - self.Watermark.TextColor = color or wx.Colour(204, 204, 204) - self.Watermark.Angle = angle - self.Watermark.Over = over - - #---------------------------------------------------------------------------- - # Standard formats - # These are meant to be illustrative rather than definitive - - @staticmethod - def Minimal(headerFontName="Arial", rowFontName="Times New Roman"): - """ - Return a minimal format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(18, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.GroupTitle.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.BLACK, 1, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Padding = (0, 12, 0, 12) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def Normal(headerFontName="Gill Sans", rowFontName="Times New Roman"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = True - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLUE, 2, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(26, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_LEFT - fmt.ListHeader.Background(wx.BLUE, wx.WHITE, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLUE, 4, toColor=wx.WHITE, space=5) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Background(wx.WHITE, wx.BLUE, space=(0, 4, 0, 4)) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.CellPadding = 2 - fmt.ColumnHeader.Background(wx.Colour(192, 192, 192)) - fmt.ColumnHeader.GridPen = wx.Pen(wx.WHITE, 1) - fmt.ColumnHeader.Padding = (0, 0, 0, 12) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.Line(wx.BOTTOM, pen=wx.Pen(wx.BLUE, 1, wx.DOT), space=3) - fmt.Row.CellPadding = 2 - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def TooMuch(headerFontName="Chiller", rowFontName="Gill Sans"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.PageHeader.TextColor = wx.WHITE - fmt.PageHeader.Background(wx.GREEN, wx.RED, space=(16, 4, 0, 4)) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(24, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_CENTER - fmt.ListHeader.Background(wx.RED, wx.GREEN, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.GroupTitle.TextColor = wx.BLUE - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.GREEN, 4, toColor=wx.WHITE, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.GREEN, 2, toColor=wx.RED, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Background(wx.Colour(255, 215, 0)) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.GridPen = wx.Pen(wx.Colour(192, 192, 192), 1) - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_SWISS, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.GridPen = wx.Pen(wx.BLUE, 1, wx.DOT) - fmt.Row.CanWrap = True - - fmt.Watermark.TextColor = wx.Colour(233, 150, 122) - - return fmt - -#---------------------------------------------------------------------------- - -class BlockFormat(object): - """ - A block format defines how a Block is formatted. - - These properties control the formatting of the matching Block: - - * CanWrap - If the text for this block cannot fit horizontally, should be wrap to a new line (True) - or should it be truncated (False)? - * Font - What font should be used to draw the text of this block - * Padding - How much padding should be applied to the block before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be - a collection of the paddings to be applied to the various sides: (left, top, right, bottom). - * TextAlignment - How should text be aligned within this block? Can be wx.ALIGN_LEFT, wx.ALIGN_CENTER, or - wx.ALIGN_RIGHT. - * TextColor - In what color should be text be drawn? - - The blocks that are based on cells (PageHeader, ColumnHeader, Row, PageFooter) can also - have the following properties set: - - * AlwaysCenter - Will the text in the cells be center aligned, regardless of other settings? - * CellPadding - How much padding should be applied to this cell before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be a - collection of the paddings to be applied to the various sides: (left, top, right, - bottom). - * GridPen - What Pen will be used to draw the grid lines of the cells? - - In addition to these properties, there are some methods which add various decorations to - the blocks: - - * Background(color=wx.BLUE, toColor=None, space=0) - - This gives the block a solid color background (or a gradient background if *toColor* - is not None). If *space* is not 0, *space* pixels will be subtracted from all sides - from the space available to the block. - - * Frame(pen=None, space=0) - - Draw a rectangle around the block in the given pen - - * Line(side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None) - - Draw a line on a given side of the block. If a pen is given, that is used to draw the - line (and the other parameters are ignored), otherwise a solid line (or a gradient - line is *toColor* is not None) of *width* pixels is drawn. - - """ - - def __init__(self): - """ - """ - self.padding = None - self.decorations = list() - self.font = wx.FFont(11, wx.FONTFAMILY_SWISS, face="Gill Sans") - self.textColor = None - self.textAlignment = wx.ALIGN_LEFT - self.alwaysCenter = False - self.canWrap = False - - #THINK: These attributes are only for grids. Should we have a GridBlockFormat object? - self.cellPadding = None - self.gridPen = None - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return the font used by this format - """ - return self.font - - def SetFont(self, font): - """ - Set the font used by this format - """ - self.font = font - - def GetTextAlignment(self): - """ - Return the alignment of text in this format - """ - return self.textAlignment - - def SetTextAlignment(self, alignment): - """ - Set the alignment of text in this format - """ - self.textAlignment = alignment - - def GetTextColor(self): - """ - Return the color of text in this format - """ - return self.textColor - - def SetTextColor(self, color): - """ - Set the color of text in this format - """ - self.textColor = color - - def GetPadding(self): - """ - Get the padding around this format - """ - return self.padding - - def SetPadding(self, padding): - """ - Set the padding around this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.padding = self._MakePadding(padding) - - def GetCellPadding(self): - """ - Get the padding around cells in this format - """ - return self.cellPadding - - def SetCellPadding(self, padding): - """ - Set the padding around cells in this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.cellPadding = self._MakePadding(padding) - - def GetGridPen(self): - """ - Return the pen used to draw a grid in this format - """ - return self.gridPen - - def SetGridPen(self, pen): - """ - Set the pen used to draw a grid in this format - """ - self.gridPen = pen - if self.gridPen: - # Other styles don't produce nice joins - self.gridPen.SetCap(wx.CAP_BUTT) - self.gridPen.SetJoin(wx.JOIN_MITER) - - def _MakePadding(self, padding): - try: - if len(padding) < 4: - return (tuple(padding) + (0, 0, 0, 0))[:4] - else: - return padding - except TypeError: - return (padding,) * 4 - - def GetAlwaysCenter(self): - """ - Return if the text controlled by this format should always be centered? - """ - return self.alwaysCenter - - def SetAlwaysCenter(self, value): - """ - Remember if the text controlled by this format should always be centered? - """ - self.alwaysCenter = value - - def GetCanWrap(self): - """ - Return if the text controlled by this format can wrap to cover more than one line? - """ - return self.canWrap - - def SetCanWrap(self, value): - """ - Remember if the text controlled by this format can wrap to cover more than one line? - """ - self.canWrap = value - - Font = property(GetFont, SetFont) - Padding = property(GetPadding, SetPadding) - TextAlignment = property(GetTextAlignment, SetTextAlignment) - TextColor = property(GetTextColor, SetTextColor) - CellPadding = property(GetCellPadding, SetCellPadding) - GridPen = property(GetGridPen, SetGridPen) - AlwaysCenter = property(GetAlwaysCenter, SetAlwaysCenter) - CanWrap = property(GetCanWrap, SetCanWrap) - - # Misspellers of the world Untie! - # Ok, ok... there're not really misspellings - just alternatives :) - TextAlign = property(GetTextAlignment, SetTextAlignment) - TextColour = property(GetTextColor, SetTextColor) - - #---------------------------------------------------------------------------- - # Calculations - - def CalculateCellPadding(self): - """ - Return a four-tuple that indicates pixel padding (left, top, right, bottom) - around cells in this format - """ - if self.CellPadding: - cellPadding = list(self.CellPadding) - else: - cellPadding = 0, 0, 0, 0 - - if self.GridPen: - penFactor = int((self.GridPen.GetWidth()+1)/2) - cellPadding = [x + penFactor for x in cellPadding] - - return cellPadding - - #---------------------------------------------------------------------------- - # Decorations - - def Add(self, decoration): - """ - Add the given decoration to those adorning blocks with this format - """ - self.decorations.append(decoration) - - - def Line(self, side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None): - """ - Add a line to our decorations. - If a pen is given, we use a straight Line decoration, otherwise we use a - coloured rectangle - """ - if pen: - self.Add(LineDecoration(side, pen, space)) - else: - self.Add(RectangleDecoration(side, None, color, toColor, width, space)) - - - def Background(self, color=wx.BLUE, toColor=None, space=0): - """ - Add a coloured background to the block - """ - self.Add(RectangleDecoration(color=color, toColor=toColor, space=space)) - - - def Frame(self, pen=None, space=0): - """ - Add a rectangle around this block - """ - self.Add(RectangleDecoration(pen=pen, space=space)) - - #---------------------------------------------------------------------------- - # Commands - - def SubtractPadding(self, bounds): - """ - Subtract any padding used by this format from the given bounds - """ - if self.padding is None: - return bounds - else: - return RectUtils.InsetRect(bounds, self.padding) - - - def SubtractDecorations(self, dc, bounds): - """ - Subtract any space used by our decorations from the given bounds - """ - for x in self.decorations: - bounds = x.SubtractFrom(dc, bounds) - return bounds - - - def DrawDecorations(self, dc, bounds, block, over): - """ - Draw our decorations on the given block - """ - for x in self.decorations: - if x.IsDrawOver() == over: - x.DrawDecoration(dc, bounds, block) - - -#---------------------------------------------------------------------------- - -class Block(object): - """ - A Block is a portion of a report that will stack vertically with other - Block. A report consists of several Blocks. - """ - - def __init__(self, engine=None): - self.engine = engine # This is also set when the block is added to a print engine - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - return self.GetFormat().GetTextColor() - - - def GetFormat(self): - """ - Return the BlockFormat object that controls the formatting of this block - """ - return self.engine.GetNamedFormat(self.__class__.__name__[:-5]) - - - def GetReducedBlockBounds(self, dc, bounds=None): - """ - Return the bounds of this block once padding and decoration have taken their toll. - """ - bounds = bounds or list(self.GetWorkBounds()) - fmt = self.GetFormat() - bounds = fmt.SubtractPadding(bounds) - bounds = fmt.SubtractDecorations(dc, bounds) - return bounds - - - def GetWorkBounds(self): - """ - Return the boundaries of the work area for this block - """ - return self.engine.workBounds - - - def IsColumnHeadingsOnEachPage(self): - """ - Should the column headers be report at the top of each new page? - """ - return self.engine.reportFormat.IsColumnHeadingsOnEachPage - - - def IncludeImages(self): - """ - Should the images from the ListCtrl be printed in the report? - """ - return self.engine.reportFormat.IncludeImages - - - def IsShrinkToFit(self): - """ - Should row blocks be shrunk to fit within the width of a page? - """ - return self.engine.reportFormat.IsShrinkToFit - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - This allows, for example, footers to have '%(currentPage)d of %(totalPages)d' - """ - return True - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateExtrasHeight(self, dc): - """ - Return the height of the padding and decorations themselves - """ - return 0 - RectUtils.Height(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateExtrasWidth(self, dc): - """ - Return the width of the padding and decorations themselves - """ - return 0 - RectUtils.Width(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - return -1 - - - def CalculateTextHeight(self, dc, txt, bounds=None, font=None): - """ - Return the height of the given txt in pixels - """ - bounds = bounds or self.GetReducedBlockBounds(dc) - font = font or self.GetFont() - dc.SetFont(font) - if self.GetFormat().CanWrap: - return WordWrapRenderer.CalculateHeight(dc, txt, RectUtils.Width(bounds)) - else: - # Calculate the height of one line. The 1000 pixel width - # ensures that 'Wy' doesn't wrap, which might happen if bounds is narrow - return WordWrapRenderer.CalculateHeight(dc, "Wy", 1000) - - - def CanFit(self, height): - """ - Can this block fit into the remaining work area on the page? - """ - return height <= RectUtils.Height(self.GetWorkBounds()) - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.ShouldPrint(): - return True - - bounds = self.CalculateBounds(dc) - if not self.CanFit(RectUtils.Height(bounds)): - return False - - if self.engine.shouldDrawBlocks: - self.PreDraw(dc, bounds) - self.Draw(dc, bounds) - self.PostDraw(dc, bounds) - - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - return True - - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If this block does not have a format, it is simply skipped - return self.GetFormat() is not None - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - return bounds - - - def ChangeWorkBoundsBy(self, amt): - """ - Move the top of our work bounds down by the given amount - """ - RectUtils.MoveTopBy(self.engine.workBounds, amt) - - - def Draw(self, dc, bounds): - """ - Draw this block and its decorations allowing for any padding - """ - fmt = self.GetFormat() - decorationBounds = fmt.SubtractPadding(bounds) - fmt.DrawDecorations(dc, decorationBounds, self, False) - textBounds = fmt.SubtractDecorations(dc, list(decorationBounds)) - self.DrawSelf(dc, textBounds) - fmt.DrawDecorations(dc, decorationBounds, self, True) - - - def PreDraw(self, dc, bounds): - """ - Executed before any drawing is done - """ - pass - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - pass - - - def PostDraw(self, dc, bounds): - """ - Executed after drawing has completed - """ - pass - - - def DrawText(self, dc, txt, bounds, font=None, alignment=wx.ALIGN_LEFT, valignment=wx.ALIGN_CENTRE, - image=None, color=None, canWrap=True, imageIndex=-1, listCtrl=None): - """ - Draw the given text in the given DC according to the given characteristics. - - This is the workhorse text drawing method for our reporting engine. - - The *font*, *alignment*, and *color* attributes are applied to the drawn text. - - If *image* is not None, it will be drawn to the left of the text. All text is indented - by the width of the image, even if the text is multi-line. - - If *imageIndex* is 0 or more and *listCtrl* is not None, the corresponding image from - the ListCtrl's small image list will be drawn to the left of the text. - - If *canWrap* is True, the text will be wrapped to fit within the given bounds. If it is False, - then the first line of *txt* will be truncated at the edge of the given *bounds*. - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - def _CalcBitmapPosition(r, height): - if valignment == wx.ALIGN_TOP: - return RectUtils.Top(r) - elif valignment == wx.ALIGN_CENTER: - return RectUtils.CenterY(r) - height / 2 - elif valignment == wx.ALIGN_BOTTOM: - return RectUtils.Bottom(r) - height - else: - return RectUtils.Top(r) - - # Draw any image - if image: - y = _CalcBitmapPosition(bounds, image.Height) - dc.DrawBitmap(image, RectUtils.Left(bounds), y) - RectUtils.MoveLeftBy(bounds, image.GetWidth()+GAP_BETWEEN_IMAGE_AND_TEXT) - elif listCtrl and imageIndex >=0: - imageList = listCtrl.GetImageList(wx.IMAGE_LIST_SMALL) - y = _CalcBitmapPosition(bounds, imageList.GetSize(0)[1]) - imageList.Draw(imageIndex, dc, RectUtils.Left(bounds), y, wx.IMAGELIST_DRAW_TRANSPARENT) - RectUtils.MoveLeftBy(bounds, imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT) - - # Draw the text - dc.SetFont(font or self.GetFont()) - dc.SetTextForeground(color or self.GetTextColor() or wx.BLACK) - if canWrap: - WordWrapRenderer.DrawString(dc, txt, bounds, alignment, valignment) - else: - WordWrapRenderer.DrawTruncatedString(dc, txt, bounds, alignment, valignment) - - - def PerformSubstitutions(self, strings): - """ - Substituted % markers in the given collection of strings. - """ - info = self.engine.GetSubstitutionInfo() - try: - # 'strings' could be a single string or a list of strings - try: - return strings % info - except TypeError: - return [x % info for x in strings] - except ValueError: - # We don't die if we get a substitution error - we just ignore it - return strings - -#---------------------------------------------------------------------------- - -class TextBlock(Block): - """ - A TextBlock prints a string objects. - """ - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If the block has no text, it should not be printed - if self.GetText(): - return Block.ShouldPrint(self) - else: - return False - - - def GetText(self): - return "Missing GetText() in class %s" % self.__class__.__name__ - - - def GetSubstitutedText(self): - """ - Return the text for this block after all markers have been substituted - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetText()) - else: - return self.GetText() - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - textHeight = self.CalculateTextHeight(dc, self.GetSubstitutedText()) - extras = self.CalculateExtrasHeight(dc) - return textHeight + extras - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - fmt = self.GetFormat() - self.DrawText(dc, self.GetSubstitutedText(), bounds, canWrap=fmt.CanWrap, alignment=fmt.TextAlignment) - - -#---------------------------------------------------------------------------- - -class CellBlock(Block): - """ - A CellBlock is a Block whose data is presented in a cell format. - """ - - def __init__(self): - self.scale = 1 - self.oldScale = 1 - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block. - """ - return list() - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return list() - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return list() - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return list() - - - #---------------------------------------------------------------------------- - # Accessing - - - def GetCombinedLists(self): - """ - Return a collection of Buckets that hold all the values of the - subclass-overridable collections above - """ - buckets = [Bucket(cellWidth=x, text="", align=None, image=None) for x in self.GetCellWidths()] - for (i, x) in enumerate(self.GetSubstitutedTexts()): - buckets[i].text = x - for (i, x) in enumerate(self.GetAlignments()): - buckets[i].align = x - - if self.IncludeImages(): - for (i, x) in enumerate(self.GetImages()): - buckets[i].image = x - - # Calculate where the cell contents should be drawn - cellPadding = self.GetFormat().CalculateCellPadding() - for x in buckets: - x.innerCellWidth = max(0, x.cellWidth - (cellPadding[0] + cellPadding[2])) - - return buckets - - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return None - - - def GetSubstitutedTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetTexts()) - else: - return self.GetTexts() - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - # If cells can wrap, figure out the tallest, otherwise we just figure out the height of one line - if self.GetFormat().CanWrap: - font = self.GetFont() - height = 0 - for x in self.GetCombinedLists(): - textWidth = x.innerCellWidth - if self.GetListCtrl() and x.image != -1: - imageList = self.GetListCtrl().GetImageList(wx.IMAGE_LIST_SMALL) - textWidth -= imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT - bounds = [0, 0, textWidth, 99999] - height = max(height, self.CalculateTextHeight(dc, x.text, bounds, font)) - else: - height = self.CalculateTextHeight(dc, "Wy") - - # We also have to allow for cell padding, on top of the normal padding and decorations - cellPadding = self.GetFormat().CalculateCellPadding() - return height + cellPadding[1] + cellPadding[3] + self.CalculateExtrasHeight(dc) - - - def CalculateWidth(self, dc): - """ - Calculate the total width of this block (cells plus padding) - """ - return sum(x.cellWidth for x in self.GetCombinedLists()) + self.CalculateExtrasWidth(dc) - - #---------------------------------------------------------------------------- - # Commands - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - bounds = RectUtils.SetWidth(bounds, self.CalculateWidth(dc)) - bounds = RectUtils.MultiplyOrigin(bounds, 1 / self.scale) - return bounds - - #def CanFit(self, height): - # height *= self.scale - # return Block.CanFit(self, height) - - def ChangeWorkBoundsBy(self, height): - """ - Change our workbounds by our scaled height - """ - Block.ChangeWorkBoundsBy(self, height * self.scale) - - - def PreDraw(self, dc, bounds): - """ - Apply our scale before performing any drawing - """ - self.oldScale = dc.GetUserScale() - dc.SetUserScale(self.scale * self.oldScale[0], self.scale * self.oldScale[1]) - - - def PostDraw(self, dc, bounds): - """ - Restore the scaling to what it used to be - """ - dc.SetUserScale(*self.oldScale) - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - cellFmt = self.GetFormat() - cellPadding = cellFmt.CalculateCellPadding() - combined = self.GetCombinedLists() - - # Calculate cell boundaries - cell = list(bounds) - RectUtils.SetWidth(cell, 0) - for x in combined: - RectUtils.SetLeft(cell, RectUtils.Right(cell)) - RectUtils.SetWidth(cell, x.cellWidth) - x.cell = list(cell) - - # Draw each cell - font = self.GetFont() - for x in combined: - cellBounds = RectUtils.InsetRect(x.cell, cellPadding) - self.DrawText(dc, x.text, cellBounds, font, x.align, imageIndex=x.image, - canWrap=cellFmt.CanWrap, listCtrl=self.GetListCtrl()) - - if cellFmt.GridPen and combined: - dc.SetPen(cellFmt.GridPen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - - top = RectUtils.Top(bounds) - bottom = RectUtils.Bottom(bounds) - - # Draw the interior dividers - for x in combined[:-1]: - right = RectUtils.Right(x.cell) - dc.DrawLine(right, top, right, bottom) - - # Draw the surrounding frame - left = RectUtils.Left(combined[0].cell) - right = RectUtils.Right(combined[-1].cell) - dc.DrawRectangle(left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ThreeCellBlock(CellBlock): - """ - A ThreeCellBlock divides its space evenly into three cells. - """ - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block - """ - # We divide the available space between the non-empty cells - numNonEmptyTexts = sum(1 for x in self.GetTexts() if x) - if not numNonEmptyTexts: - return (0, 0, 0) - - widths = list() - width = round(RectUtils.Width(self.GetWorkBounds()) / numNonEmptyTexts) - for x in self.GetTexts(): - if x: - widths.append(width) - else: - widths.append(0) - return widths - - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return (wx.ALIGN_LEFT, wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_RIGHT) - -#---------------------------------------------------------------------------- - -class ReportBlock(Block): - """ - A ReportBlock is boot strap Block that represents an entire report. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.engine.listCtrls: - return True - - # Print each ListView. Each list but the first starts on a separate page - self.engine.AddBlock(ListBlock(*self.engine.listCtrls[0])) - for (lv, title) in self.engine.listCtrls[1:]: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListBlock(lv, title)) - - return True - - -#---------------------------------------------------------------------------- - -class PageHeaderBlock(ThreeCellBlock): - """ - A PageHeaderBlock appears at the top of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageHeader - - - -#---------------------------------------------------------------------------- - -class PageFooterBlock(ThreeCellBlock): - """ - A PageFooterBlock appears at the bottom of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageFooter - - - #---------------------------------------------------------------------------- - # Printing - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - # Draw the footer at the bottom of the page - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - return [RectUtils.Left(bounds), RectUtils.Bottom(bounds) - height, - RectUtils.Width(bounds), height] - - - def ChangeWorkBoundsBy(self, height): - """ - The footer changes the bottom of the work bounds - """ - RectUtils.MoveBottomBy(self.engine.workBounds, -height) - - -#---------------------------------------------------------------------------- - -class PageBreakBlock(Block): - """ - A PageBreakBlock acts a page break. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - # Completely fill the remaining area on the page, forcing a page break - bounds = self.GetWorkBounds() - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - - return True - -#---------------------------------------------------------------------------- - -class RunningBlockPusher(Block): - """ - A RunningBlockPusher pushes (or pops) a running block onto the stack when it is executed. - """ - - def __init__(self, block, push=True): - """ - """ - self.block = block - self.push = push - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if self.push: - self.engine.AddRunningBlock(self.block) - else: - self.engine.RemoveRunningBlock(self.block) - - return True - -#---------------------------------------------------------------------------- - -class ListBlock(Block): - """ - A ListBlock handles the printing of an entire ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - cellWidths = self.CalculateCellWidths() - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - # Break the list into vertical slices. Each one but the first will be placed on a - # new page. - first = True - for (left, right) in self.CalculateSlices(boundsWidth, cellWidths): - if not first: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListHeaderBlock(self.lv, self.title)) - self.engine.AddBlock(ListSliceBlock(self.lv, left, right, cellWidths)) - self.engine.AddBlock(ListFooterBlock(self.lv, "")) - first = False - - return True - - - def CalculateCellWidths(self): - """ - Return a list of the widths of the cells in this lists - """ - columnHeaderFmt = self.engine.GetNamedFormat("ColumnHeader") - cellPadding = columnHeaderFmt.CalculateCellPadding() - padding = cellPadding[0] + cellPadding[2] - return [self.lv.GetColumnWidth(i) + padding for i in range(self.lv.GetColumnCount())] - - - def CalculateSlices(self, maxWidth, columnWidths): - """ - Return a list of integer pairs, where each pair represents - the left and right columns that can fit into the width of one page - """ - firstColumn = 0 - - # If a GroupListView has a column just for the expand/collapse, don't include it - if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: - firstColumn = 1 - - # If we are shrinking to fit or all the columns fit, just return all columns - if self.IsShrinkToFit() or (sum(columnWidths)) <= maxWidth: - return [ [firstColumn, len(columnWidths)-1] ] - - pairs = list() - left = firstColumn - right = firstColumn - while right < len(columnWidths): - if (sum(columnWidths[left:right+1])) > maxWidth: - if left == right: - pairs.append([left, right]) - left += 1 - right += 1 - else: - pairs.append([left, right-1]) - left = right - else: - right += 1 - - if left < len(columnWidths): - pairs.append([left, right-1]) - - return pairs - - -#---------------------------------------------------------------------------- - -class ListHeaderBlock(TextBlock): - """ - A ListHeaderBlock is the title that comes before an ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.title - -#---------------------------------------------------------------------------- - -class ListFooterBlock(TextBlock): - """ - A ListFooterBlock is the text that comes after an ListCtrl. - """ - - def __init__(self, lv, text): - self.lv = lv - self.text = text - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.text - - -#---------------------------------------------------------------------------- - -class GroupTitleBlock(TextBlock): - """ - A GroupTitleBlock is the title that comes before a list group. - """ - - def __init__(self, lv, group): - self.lv = lv - self.group = group - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.group.title - -#---------------------------------------------------------------------------- - -class ListSliceBlock(Block): - """ - A ListSliceBlock prints a vertical slice of an ListCtrl. - """ - - def __init__(self, lv, left, right, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # If we are shrinking our cells, calculate the shrink factor - if self.IsShrinkToFit(): - scale = self.CalculateShrinkToFit(dc) - else: - scale = 1 - - headerBlock = ColumnHeaderBlock(self.lv, self.left, self.right, scale, self.allCellWidths) - self.engine.AddBlock(headerBlock) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock)) - - # Are we printing a GroupListView? - # We can't use isinstance() since ObjectListView may not be installed - if hasattr(self.lv, "GetShowGroups") and self.lv.GetShowGroups(): - self.engine.AddBlock(GroupListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - else: - self.engine.AddBlock(ListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock, False)) - - return True - - def CalculateShrinkToFit(self, dc): - """ - How much do we have to shrink our rows by to fit onto the page? - """ - block = ColumnHeaderBlock(self.lv, self.left, self.right, 1, self.allCellWidths) - block.engine = self.engine - rowWidth = block.CalculateWidth(dc) - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - if rowWidth > boundsWidth: - return float(boundsWidth) / float(rowWidth) - else: - return 1 - -#---------------------------------------------------------------------------- - -class ColumnBasedBlock(CellBlock): - """ - A ColumnBasedBlock is a cell block that takes its width from the - columns of a ListCtrl. - - This is an abstract class - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return self.lv - - - def GetCellWidths(self): - """ - Return the widths of the cells used in this block - """ - #return [self.allCellWidths[i] for i in range(self.left, self.right+1)] - return self.allCellWidths[self.left:self.right+1] - - #---------------------------------------------------------------------------- - # Utiltities - - def GetColumnAlignments(self, lv, left, right): - """ - Return the alignments of the given slice of columns - """ - listAlignments = [lv.GetColumn(i).GetAlign() for i in range(left, right+1)] - mapping = { - wx.LIST_FORMAT_LEFT: wx.ALIGN_LEFT, - wx.LIST_FORMAT_RIGHT: wx.ALIGN_RIGHT, - wx.LIST_FORMAT_CENTRE: wx.ALIGN_CENTRE, - } - return [mapping[x] for x in listAlignments] - - - -#---------------------------------------------------------------------------- - -class ColumnHeaderBlock(ColumnBasedBlock): - """ - A ColumnHeaderBlock prints a portion of the columns header in a ListCtrl. - """ - - #---------------------------------------------------------------------------- - # Accessing - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetColumn(i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - if self.GetFormat().AlwaysCenter: - return [wx.ALIGN_CENTRE for i in range(self.left, self.right+1)] - else: - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - # For some reason, columns return 0 when they have no image, rather than -1 like they should - images = list() - for i in range(self.left, self.right+1): - imageIndex = self.lv.GetColumn(i).GetImage() - if imageIndex == 0: - imageIndex = -1 - images.append(imageIndex) - return images - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - -#---------------------------------------------------------------------------- - -class ListRowsBlock(Block): - """ - A ListRowsBlock prints rows of an ListCtrl. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex < self.totalRows: - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - -#---------------------------------------------------------------------------- - -class GroupListRowsBlock(Block): - """ - A GroupListRowsBlock prints rows of an GroupListView. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv # Must be a GroupListView - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex >= self.totalRows: - return True - - # If GetObjectAt() return an object, then it's a normal row. - # Otherwise, if the innerList object isn't None, it must be a ListGroup - # We can't use isinstance(x, GroupListView) because ObjectListView may not be installed - if self.lv.GetObjectAt(self.currentIndex): - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - elif self.lv.innerList[self.currentIndex]: - self.engine.AddBlock(GroupTitleBlock(self.lv, self.lv.innerList[self.currentIndex])) - - # Schedule the printing of the remaining rows - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - - -#---------------------------------------------------------------------------- - -class RowBlock(ColumnBasedBlock): - """ - A RowBlock prints a vertical slice of a single row of an ListCtrl. - """ - - def __init__(self, lv, rowIndex, left, right, scale, allCellWidths): - self.lv = lv - self.rowIndex = rowIndex - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - font = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what font is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasFont(): - font = attr.GetFont() - else: - font = listCtrl.GetItemFont(self.rowIndex) - - if font and font.IsOk(): - return font - else: - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - color = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what text colour is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasTextColour(): - color = attr.GetTextColour() - else: - color = listCtrl.GetItemTextColour(self.rowIndex) - - if color and color.IsOk(): - return color - else: - return self.GetFormat().GetTextColor() - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - #---------------------------------------------------------------------------- - # Overrides for CellBlock - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetItem(self.rowIndex, i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return [self.lv.GetItem(self.rowIndex, i).GetImage() for i in range(self.left, self.right+1)] - - #def GetTexts(self): - # """ - # Return a list of the texts that should be drawn with the cells - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetStringValueAt(modelObjects, i) for i in range(self.left, self.right+1)] - # - #def GetAlignments(self): - # """ - # Return a list indicating how the text within each cell is aligned. - # """ - # return [self.lv.columns[i].GetAlignment() for i in range(self.left, self.right+1)] - # - #def GetImages(self): - # """ - # Return a list of the images that should be drawn in each cell - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetImageAt(modelObjects, i) for i in range(self.left, self.right+1)] - - -#---------------------------------------------------------------------------- - -class Decoration(object): - """ - A Decoration add some visual effect to a Block (e.g. borders, - background image, watermark). They can also reserve a chunk of their Blocks - space for their own use. - - Decorations are added to a BlockFormat which is then applied to a ReportBlock. - - All the decorations for a block are drawn into the same area. If two decorations are - added, they will draw over the top of each other. This is normally what is expected, - but may sometimes be surprising. For example, if you add two Lines to the left of the - same block, they will draw over the top of each other. - - """ - - #---------------------------------------------------------------------------- - # Accessing - - def IsDrawOver(self): - """ - Return True if this decoration should be drawn over it's block. If not, - it will be drawn underneath - """ - return False - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - return bounds - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - pass - -#---------------------------------------------------------------------------- - -class RectangleDecoration(Decoration): - """ - A RectangleDecoration draw a rectangle around or on the side of a block. - - The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. - - """ - - def __init__(self, side=None, pen=None, color=None, toColor=None, width=0, space=0): - """ - If color is None, the rectangle will be hollow. - If toColor is None, the rectangle will be filled with "color" otherwise it will be - gradient-filled. - If pen is not None, the rectangle will be framed with that pen. - If no side is given, the rectangle will be drawn around the block. In that case, - space can be a list giving the space on each side. - """ - self.side = side - self.pen = pen - self.color = color - self.toColor = toColor - self.width = width - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - if self.side is None: - return RectUtils.InsetBy(RectUtils.InsetBy(bounds, self.space), self.width) - - inset = self.space + self.width - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - rect = self._CalculateRect(bounds) - - if self.color: - if self.toColor is None: - dc.SetPen(wx.TRANSPARENT_PEN) - dc.SetBrush(wx.Brush(self.color)) - dc.DrawRectangle(*rect) - else: - dc.GradientFillLinear(wx.Rect(*rect), self.color, self.toColor) - - if self.pen: - dc.SetPen(self.pen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.DrawRectangle(*rect) - - - def _CalculateRect(self, bounds): - """ - Calculate the rectangle that this decoration is going to paint - """ - if self.side is None: - return bounds - if self.side == wx.LEFT: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.RIGHT: - return (RectUtils.Right(bounds) - self.width, RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.TOP: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), RectUtils.Width(bounds), self.width) - if self.side == wx.BOTTOM: - return (RectUtils.Left(bounds), RectUtils.Bottom(bounds) - self.width, RectUtils.Width(bounds), self.width) - - return bounds - -#---------------------------------------------------------------------------- - -class LineDecoration(Decoration): - """ - A LineDecoration draws a line on the side of a decoration. - """ - - def __init__(self, side=wx.BOTTOM, pen=None, space=0): - self.side = side - self.pen = pen - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - inset = self.space - if self.pen is not None: - inset += self.pen.GetWidth() - - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - if self.pen == None: - return - - if self.side == wx.LEFT: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.BottomLeft(bounds) - elif self.side == wx.RIGHT: - pt1 = RectUtils.TopRight(bounds) - pt2 = RectUtils.BottomRight(bounds) - elif self.side == wx.TOP: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.TopRight(bounds) - elif self.side == wx.BOTTOM: - pt1 = RectUtils.BottomLeft(bounds) - pt2 = RectUtils.BottomRight(bounds) - - dc.SetPen(self.pen) - dc.DrawLine(pt1[0], pt1[1], pt2[0], pt2[1]) - -#---------------------------------------------------------------------------- - -class WatermarkDecoration(Decoration): - """ - A WatermarkDecoration draws an angled line of text over each page. - - The watermark is rotated around the center of the page. - - If *over* is True, the watermark will be printed on top of the page. - Otherwise, it will be printed under the page. - - """ - - def __init__(self, text, font=None, color=None, angle=30, over=True): - """ - """ - self.text = text - self.color = color or wx.Color(128, 128, 128, 128) - self.font = font or wx.FFont(128, wx.SWISS, 0) - self.angle = angle - self.over = over - - def IsDrawOver(self): - """ - Should this decoration be drawn over the rest of page? - """ - return self.over - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - dc.SetFont(self.font) - dc.SetTextForeground(self.color) - - # Rotate the text around the center of the page - cx, cy = RectUtils.Center(bounds) - w, h = dc.GetTextExtent(self.text) - - x = cx - w/2 - y = cy - h/2 + (w/2 * math.sin(math.radians(self.angle))) - - dc.DrawRotatedText(self.text, x, y, self.angle) - -#---------------------------------------------------------------------------- - -class ImageDecoration(Decoration): - """ - A ImageDecoration draws an image over (or under) the given block. - - NB: Printer contexts do not honor alpha channels. - """ - - def __init__(self, image=None, horizontalAlign=wx.CENTER, verticalAlign=wx.CENTER, over=True): - """ - image must be either an wx.Image or a wx.Bitmap - """ - self.horizontalAlign = horizontalAlign - self.verticalAlign = verticalAlign - self.over = True - - self.bitmap = image - if isinstance(image, wx.Image): - self.bitmap = wx.BitmapFromImage(image) - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - if not self.bitmap: - return - - if self.horizontalAlign == wx.LEFT: - x = RectUtils.Left(bounds) - elif self.horizontalAlign == wx.RIGHT: - x = RectUtils.Right(bounds) - self.bitmap.Width - else: - x = RectUtils.CenterX(bounds) - self.bitmap.Width/2 - - if self.verticalAlign == wx.TOP: - y = RectUtils.Top(bounds) - elif self.verticalAlign == wx.BOTTOM: - y = RectUtils.Bottom(bounds) - self.bitmap.Height - else: - y = RectUtils.CenterY(bounds) - self.bitmap.Height/2 - - dc.DrawBitmap(self.bitmap, x, y, True) - - -#---------------------------------------------------------------------------- - -class Bucket(object): - """ - General purpose, hold-all data object - """ - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - strs = ["%s=%r" % kv for kv in self.__dict__.items()] - return "Bucket(" + ", ".join(strs) + ")" - -#---------------------------------------------------------------------------- - -class RectUtils: - """ - Static rectangle utilities. - - Rectangles are a list or tuple of 4-elements: [left, top, width, height] - """ - - #---------------------------------------------------------------------------- - # Accessing - - @staticmethod - def Left(r): return r[0] - - @staticmethod - def Top(r): return r[1] - - @staticmethod - def Width(r): return r[2] - - @staticmethod - def Height(r): return r[3] - - @staticmethod - def Right(r): return r[0] + r[2] - - @staticmethod - def Bottom(r): return r[1] + r[3] - - @staticmethod - def TopLeft(r): return [r[0], r[1]] - - @staticmethod - def TopRight(r): return [r[0] + r[2], r[1]] - - @staticmethod - def BottomLeft(r): return [r[0], r[1] + r[3]] - - @staticmethod - def BottomRight(r): return [r[0] + r[2], r[1] + r[3]] - - @staticmethod - def CenterX(r): return r[0] + r[2]/2 - - @staticmethod - def CenterY(r): return r[1] + r[3]/2 - - @staticmethod - def Center(r): return [r[0] + r[2]/2, r[1] + r[3]/2] - - #---------------------------------------------------------------------------- - # Modifying - - @staticmethod - def SetLeft(r, l): - r[0] = l - return r - - @staticmethod - def SetTop(r, t): - r[1] = t - return r - - @staticmethod - def SetWidth(r, w): - r[2] = w - return r - - @staticmethod - def SetHeight(r, h): - r[3] = h - return r - - @staticmethod - def MoveLeftBy(r, delta): - r[0] += delta - r[2] -= delta - return r - - @staticmethod - def MoveTopBy(r, delta): - r[1] += delta - r[3] -= delta - return r - - @staticmethod - def MoveRightBy(r, delta): - r[2] += delta - return r - - @staticmethod - def MoveBottomBy(r, delta): - r[3] += delta - return r - - #---------------------------------------------------------------------------- - # Calculations - - @staticmethod - def InsetBy(r, delta): - if delta is None: - return r - try: - delta[0] # is it indexable? - return RectUtils.InsetRect(r, delta) - except: - return RectUtils.InsetRect(r, (delta, delta, delta, delta)) - - @staticmethod - def InsetRect(r, r2): - if r2 is None: - return r - else: - return [r[0] + r2[0], r[1] + r2[1], r[2] - (r2[0] + r2[2]), r[3] - (r2[1] + r2[3])] - - @staticmethod - def MultiplyOrigin(r, factor): - return [r[0]*factor, r[1]*factor, r[2], r[3]] - -#---------------------------------------------------------------------------- -# TESTING ONLY -#---------------------------------------------------------------------------- - -if __name__ == '__main__': - import wx - from ObjectListView import ObjectListView, FastObjectListView, GroupListView, ColumnDefn - - # Where can we find the Example module? - import sys - sys.path.append("../Examples") - - import ExampleModel - import ExampleImages - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - #self.lv = ObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - self.lv = GroupListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - #self.lv = FastObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.lv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - musicImage = self.lv.AddImages(ExampleImages.getMusic16Bitmap(), ExampleImages.getMusic32Bitmap()) - artistImage = self.lv.AddImages(ExampleImages.getUser16Bitmap(), ExampleImages.getUser32Bitmap()) - - self.lv.SetColumns([ - ColumnDefn("Title", "left", 200, "title", imageGetter=musicImage), - ColumnDefn("Artist", "left", 150, "artist", imageGetter=artistImage), - ColumnDefn("Last Played", "left", 100, "lastPlayed"), - ColumnDefn("Size", "center", 100, "sizeInBytes"), - ColumnDefn("Rating", "center", 100, "rating"), - ]) - - #self.lv.CreateCheckStateColumn() - self.lv.SetSortColumn(self.lv.columns[2]) - self.lv.SetObjects(ExampleModel.GetTracks()) - - wx.CallLater(50, self.run) - - def run(self): - printer = ListCtrlPrinter(self.lv, "Playing with ListCtrl Printing") - printer.ReportFormat = ReportFormat.Normal() - printer.ReportFormat.WatermarkFormat(over=True) - printer.ReportFormat.IsColumnHeadingsOnEachPage = True - - #printer.ReportFormat.Page.Add(ImageDecoration(ExampleImages.getGroup32Bitmap(), wx.RIGHT, wx.BOTTOM)) - - #printer.PageHeader("%(listTitle)s") # nice idea but not possible at the moment - printer.PageHeader = "Playing with ListCtrl Printing" - printer.PageFooter = ("Bright Ideas Software", "%(date)s", "%(currentPage)d of %(totalPages)d") - printer.Watermark = "Sloth!" - - #printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() +# -*- coding: utf-8 -*- +#!/usr/bin/env python +#---------------------------------------------------------------------------- +# Name: ListCtrlPrinter.py +# Author: Phillip Piper +# Created: 17 July 2008 +# SVN-ID: $Id$ +# Copyright: (c) 2008 by Phillip Piper, 2008 +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/07/17 JPP Inspired beginning +#---------------------------------------------------------------------------- +# To do: +# - persistence of ReportFormat +# - allow cell contents to be vertically aligned +# - in light of several of the "to dos", consider having CellBlockFormat object +# - write a data abstraction layer between the printer and the ListCtrl. +# This layer could understand ObjectListViews and be more efficient. +# - consider if this could be made to work with a wx.Grid (needs data abstraction layer) + +# Known issues: +# - the 'space' setting on decorations is not intuitive + + +""" +An ``ListCtrlPrinter`` takes an ``ObjectListView`` or ``wx.ListCtrl`` and turns it into a +pretty report. + +As always, the goal is for this to be as easy to use as possible. A typical +usage should be as simple as:: + + printer = ListCtrlPrinter(self.myOlv, "My Report Title") + printer.PrintPreview() + +This will produce a report with reasonable formatting. The formatting of a report is +controlled completely by the ReportFormat object of the ListCtrlPrinter. To change the appearance +of the report, you change the settings in this object. + +A report consists of various sections (called "blocks") and each of these blocks has a +matching BlockFormat object in the ReportFormat. So, to modify the format of the +page header, you change the ReportFormat.PageHeader object. + +A ReportFormat object has the following properties which control the appearance of the matching +sections of the report: + +* PageHeader +* ListHeader +* ColumnHeader +* GroupTitle +* Row +* ListFooter +* PageFooter +* Page + +These properties return BlockFormat objects, which have the following properties: + +* AlwaysCenter +* CanWrap +* Font +* Padding +* TextAlignment +* TextColor + +* CellPadding +* GridPen + +Implementation +============== + +A ``ListCtrlPrinter`` is a *Facade* over two classes: + - ``ListCtrlPrintout``, which handles the interface to the wx printing subsystem + - ``ReportEngine``, which does the actual work of creating the report + +The ``ListCtrlPrintout`` handles all the details of the wx printing subsystem. In +particular, it configures the printing DC so that its origin and scale are correct. This +enables the ``ReportEngine`` to simply render the report without knowing the +characteristics of the underlying printer DPI, unprintable region, or the scale of a print +preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like +actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards +to the ``ReportEngine``). + +The ``ReportEngine`` uses a block structure approach to reports. Each element of +a report is a "block", which stacks vertically with other blocks. + +When a block is printed, it can either render itself into a given DC or it can replace +itself in the report structure with one or more other blocks. A ``TextBlock`` renders +itself by drawing into the DC. In contrast, the block that prints a ``ListCtrl`` replaces +itself with a ``ListHeaderBlock``, a ``ColumnHeaderBlock``, ``ListRowsBlock`` and finally a +``ListFooterBlock``. + +The blocks describe the structure of the report. The formatting of a report is +controlled by ``BlockFormat`` objects. The ``ReportFormat`` contains a ``BlockFormat`` +object for each formattable section of the report. + +Each section must be formatted in same fashion, i.e. all column headers will look the same, +all page footer will look the same. There is no way to make the first footer look one way, +and the second look a different way. +""" + +import datetime +import math + +import wx + +from WordWrapRenderer import WordWrapRenderer + +#---------------------------------------------------------------------------- + +class ListCtrlPrinter(object): + """ + An ListCtrlPrinter creates a pretty report from an ObjectListView/ListCtrl. + + """ + + def __init__(self, listCtrl=None, title="ListCtrl Printing"): + """ + """ + self.printout = ListCtrlPrintout(self) + self.engine = ReportEngine() + if listCtrl is not None: + self.AddListCtrl(listCtrl, title) + + #---------------------------------------------------------------------------- + # Accessing + + def GetPageFooter(self): + """ + Return a 3-tuple of the texts that will be shown in left, center, and right cells + of the page footer + """ + return self.engine.pageFooter + + def SetPageFooter(self, leftText="", centerText="", rightText=""): + """ + Set the texts that will be shown in various cells of the page footer. + + leftText can be a string or a 3-tuple of strings. + """ + if isinstance(leftText, (tuple, list)): + self.engine.pageFooter = leftText + else: + self.engine.pageFooter = (leftText, centerText, rightText) + + def GetPageHeader(self): + """ + Return a 3-tuple of the texts that will be shown in left, center, and right cells + of the page header + """ + return self.engine.pageHeader + + def SetPageHeader(self, leftText="", centerText="", rightText=""): + """ + Set the texts that will be shown in various cells of the page header + """ + if isinstance(leftText, (tuple, list)): + self.engine.pageHeader = leftText + else: + self.engine.pageHeader = (leftText, centerText, rightText) + + def GetPrintData(self): + """ + Return the wx.PrintData that controls the printing of this report + """ + return self.printout.printData + + def GetReportFormat(self): + """ + Return the ReportFormat object that controls the appearance of this printout + """ + return self.engine.reportFormat + + def SetReportFormat(self, fmt): + """ + Set the ReportFormat object that controls the appearance of this printout + """ + self.engine.reportFormat = fmt + + def GetWatermark(self, txt): + """ + Get the text that will be printed as a watermark on the report + """ + return self.engine.watermark + + def SetWatermark(self, txt): + """ + Set the text that will be printed as a watermark on the report + """ + self.engine.watermark = txt + + ReportFormat = property(GetReportFormat, SetReportFormat) + PageFooter = property(GetPageFooter, SetPageFooter) + PageHeader = property(GetPageHeader, SetPageHeader) + PrintData = property(GetPrintData) + Watermark = property(GetWatermark, SetWatermark) + + #---------------------------------------------------------------------------- + # Setup + + def AddListCtrl(self, listCtrl, title=None): + """ + Add the given list to those that will be printed by this report. + """ + self.engine.AddListCtrl(listCtrl, title) + + + def Clear(self): + """ + Remove all ListCtrls from this printer + """ + self.engine.ClearListCtrls() + + #---------------------------------------------------------------------------- + # Printing Commands + + def PageSetup(self, parent=None): + """ + Show a Page Setup dialog that will change the configuration of this printout + """ + self.printout.PageSetup(parent) + + + def PrintPreview(self, parent=None, title="ListCtrl Print Preview", bounds=(20, 50, 800, 800)): + """ + Show a Print Preview of this report + """ + self.printout.PrintPreview(parent, title, bounds) + + + def Print(self, parent=None): + """ + Print this report to the selected printer + """ + self.printout.DoPrint(parent) + + #---------------------------------------------------------------------------- + # Callbacks + # These methods are invoked by the ListCtrlPrintout when required + + def CalculateTotalPages(self, dc, bounds): + """ + Do the work of calculating how many pages this report will occupy? + + This is expensive because it basically prints the whole report. + """ + return self.engine.CalculateTotalPages(dc, bounds) + + + def StartPrinting(self): + """ + A new print job is about to begin. + """ + self.engine.StartPrinting() + + + def PrintPage(self, dc, pageNumber, bounds): + """ + Print the given page on the given device context. + """ + self.engine.PrintPage(dc, pageNumber, bounds) + +#---------------------------------------------------------------------------- + +class ReportEngine(object): + """ + A ReportEngine handles all the work of actually producing a report. + + Public instance variables (all others should be treated as private): + + * dateFormat + When the current date/time is substituted into report text, how should + the datetime be formatted? This must be a valid format string for the + strftime() method. + Default: "%x %X" + + """ + + def __init__(self): + """ + """ + self.currentPage = -1 + self.totalPages = -1 + self.blocks = list() + self.blockInsertionIndex = 0 + self.listCtrls = list() + self.dateFormat = "%x %X" + + self.reportFormat = ReportFormat.Normal() + + self.watermark = "" + self.pageHeader = list() + self.pageFooter = list() + #self.isPrintSelectionOnly = False # not currently implemented + + # If this is False, no drawing should be done. The engine is either counting + # pages, or skipping to a specific page + self.shouldDrawBlocks = True + + #---------------------------------------------------------------------------- + # Accessing + + def GetNamedFormat(self, name): + """ + Return the given format + """ + return self.reportFormat.GetNamedFormat(name) + + + def GetTotalPages(self): + """ + Return the total number of pages that this report will produce. + + CalculateTotalPages() must be called before this is accurate. + """ + return self.totalPages + + + def GetSubstitutionInfo(self): + """ + Return a dictionary that can be used for substituting values into strings + """ + dateString = datetime.datetime.now().strftime(self.dateFormat) + info = { + "currentPage": self.currentPage, + "date": dateString, + "totalPages": self.totalPages, + } + return info + + #---------------------------------------------------------------------------- + # Calculating + + def CalculateTotalPages(self, dc, bounds): + """ + Do the work of calculating how many pages this report will occupy? + + This is expensive because it basically prints the whole report. + """ + self.StartPrinting() + self.totalPages = 1 + self.shouldDrawBlocks = False + while self.PrintOnePage(dc, self.totalPages, bounds): + self.totalPages += 1 + self.shouldDrawBlocks = True + return self.totalPages + + #---------------------------------------------------------------------------- + # Commands + + def AddBlock(self, block): + """ + Add the given block at the current insertion point + """ + self.blocks.insert(self.blockInsertionIndex, block) + self.blockInsertionIndex += 1 + block.engine = self + + + def AddListCtrl(self, listCtrl, title=None): + """ + Add the given list to those that will be printed by this report. + """ + if listCtrl.InReportView(): + self.listCtrls.append([listCtrl, title]) + + + def ClearListCtrls(self): + """ + Remove all ListCtrls from this report. + """ + self.listCtrls = list() + + + def DropCurrentBlock(self): + """ + Remove the current block from our list of blocks + """ + self.blocks.pop(0) + self.blockInsertionIndex = 1 + + #---------------------------------------------------------------------------- + # Printing + + def StartPrinting(self): + """ + Initial a print job on this engine + """ + self.currentPage = 0 + self.blockInsertionIndex = 0 + self.blocks = list() + self.AddBlock(ReportBlock()) + self.runningBlocks = list() + self.AddRunningBlock(PageHeaderBlock()) + self.AddRunningBlock(PageFooterBlock()) + + if self.watermark: + self._CreateReplaceWatermarkDecoration() + + + def AddRunningBlock(self, block): + """ + A running block is printed on every page until it is removed + """ + self.runningBlocks.append(block) + block.engine = self + + + def RemoveRunningBlock(self, block): + """ + A running block is printed on every page until it is removed + """ + self.runningBlocks.remove(block) + + + def PrintPage(self, dc, pageNumber, bounds): + """ + Print the given page on the given device context. + """ + # If the request page isn't next in order, we have to restart + # the printing process and advance until we reach the desired page. + if pageNumber != self.currentPage + 1: + self.StartPrinting() + self.shouldDrawBlocks = False + for i in range(1, pageNumber): + self.PrintOnePage(dc, i, bounds) + self.shouldDrawBlocks = True + + return self.PrintOnePage(dc, pageNumber, bounds) + + + def PrintOnePage(self, dc, pageNumber, bounds): + """ + Print the current page on the given device context. + + Return true if there is still more to print. + """ + # Initialize state + self.currentPage = pageNumber + self.pageBounds = list(bounds) + self.workBounds = list(self.pageBounds) + self.SubtractDecorations(dc) + + # Print page adornments, including under-text decorations + self.DrawPageDecorations(dc, False) + for x in self.runningBlocks: + x.Print(dc) + + # Print blocks until they won't fit or we run out of blocks + while len(self.blocks) and self.blocks[0].Print(dc): + self.DropCurrentBlock() + + # Finally, print over-the-text decorations + self.DrawPageDecorations(dc, True) + + return len(self.blocks) > 0 + + + def SubtractDecorations(self, dc): + """ + # Subtract the area used from the work area + """ + fmt = self.GetNamedFormat("Page") + self.workBounds = fmt.SubtractDecorations(dc, self.workBounds) + + + def DrawPageDecorations(self, dc, over): + """ + Draw the page decorations + """ + if not self.shouldDrawBlocks: + return + + fmt = self.GetNamedFormat("Page") + bounds = list(self.pageBounds) + fmt.DrawDecorations(dc, bounds, self, over) + + + def _CreateReplaceWatermarkDecoration(self): + """ + Create a watermark decoration, replacing any existing watermark + """ + pageFmt = self.GetNamedFormat("Page") + pageFmt.decorations = [x for x in pageFmt.decorations if not isinstance(x, WatermarkDecoration)] + + watermarkFmt = self.GetNamedFormat("Watermark") + pageFmt.Add(WatermarkDecoration(self.watermark, font=watermarkFmt.Font, + color=watermarkFmt.TextColor, angle=watermarkFmt.Angle, + over=watermarkFmt.Over)) + +#---------------------------------------------------------------------------- + +class ListCtrlPrintout(wx.Printout): + """ + An ListCtrlPrintout is the interface between the wx printing system + and ListCtrlPrinter. + """ + + def __init__(self, olvPrinter, margins=None): + """ + """ + wx.Printout.__init__(self) + self.olvPrinter = olvPrinter + self.margins = margins or (wx.Point(15, 15), wx.Point(15, 15)) + self.totalPages = -1 + + self.printData = wx.PrintData() + self.printData.SetPrinterName("") # Use default printer + self.printData.SetPaperId(wx.PAPER_A4) + self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) + + + #---------------------------------------------------------------------------- + # Accessing + + def HasPage(self, page): + """ + Return true if this printout has the given page number + """ + return page <= self.totalPages + + + def GetPageInfo(self): + """ + Return a 4-tuple indicating the ... + """ + return (1, self.totalPages, 1, 1) + + + def GetPrintPreview(self): + """ + Get a wxPrintPreview of this report + """ + data = wx.PrintDialogData(self.printData) + forViewing = ListCtrlPrintout(self.olvPrinter, self.margins) + forPrinter = ListCtrlPrintout(self.olvPrinter, self.margins) + preview = wx.PrintPreview(forViewing, forPrinter, data) + return preview + + #---------------------------------------------------------------------------- + # Commands + + def PageSetup(self, parent): + """ + Show a Page Setup dialog that will change the configuration of this printout + """ + data = wx.PageSetupDialogData() + data.SetPrintData(self.printData) + data.SetDefaultMinMargins(True) + data.SetMarginTopLeft(self.margins[0]) + data.SetMarginBottomRight(self.margins[1]) + dlg = wx.PageSetupDialog(parent, data) + if dlg.ShowModal() == wx.ID_OK: + data = dlg.GetPageSetupData() + self.printData = wx.PrintData(data.GetPrintData()) + self.printData.SetPaperId(data.GetPaperId()) + self.margins = (data.GetMarginTopLeft(), data.GetMarginBottomRight()) + dlg.Destroy() + + + + def PrintPreview(self, parent, title, bounds): + """ + Show a Print Preview of this report + """ + self.preview = self.GetPrintPreview() + + if not self.preview.Ok(): + return False + + pfrm = wx.PreviewFrame(self.preview, parent, title) + + pfrm.Initialize() + pfrm.SetPosition(bounds[0:2]) + pfrm.SetSize(bounds[2:4]) + pfrm.Show(True) + + return True + + + def DoPrint(self, parent): + """ + Send the report to the configured printer + """ + try: + pdd = wx.PrintDialogData(self.printData) + printer = wx.Printer(pdd) + + if printer.Print(parent, self, True): + self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) + else: + wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) + finally: + pdd.Destroy() + + + #---------------------------------------------------------------------------- + # Event handlers + + def OnPreparePrinting(self): + """ + Prepare for printing. This event is sent before any of the others + """ + dc = self.GetDC() + self.SetScaleAndBounds(dc) + self.totalPages = self.olvPrinter.CalculateTotalPages(dc, self.bounds) + self.olvPrinter.StartPrinting() + + def OnBeginDocument(self, start, end): + """ + Begin printing one copy of the document. Return False to cancel the job + """ + return super(ListCtrlPrintout, self).OnBeginDocument(start, end) + + def OnEndDocument(self): + super(ListCtrlPrintout, self).OnEndDocument() + + def OnBeginPrinting(self): + super(ListCtrlPrintout, self).OnBeginPrinting() + + def OnEndPrinting(self): + super(ListCtrlPrintout, self).OnEndPrinting() + + def OnPrintPage(self, page): + """ + Do the work of printing the given page number. + """ + # We bounce this back to the printer facade + dc = self.GetDC() + self.SetScaleAndBounds(dc) + return self.olvPrinter.PrintPage(dc, page, self.bounds) + + def SetScaleAndBounds(self, dc): + """ + Calculate the scale required for our printout to match what appears on screen, + and the bounds that will be effective at that scale and margins + """ + # This code comes from Robin Dunn's "wxPython In Action." + # Without that, I would never have figured this out. + ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() + ppiScreenX, ppiScreenY = self.GetPPIScreen() + logicalScale = float(ppiPrinterX) / float(ppiScreenX) + pw, ph = self.GetPageSizePixels() + dw, dh = dc.GetSize() + scale = logicalScale * float(dw)/float(pw) + dc.SetUserScale(scale, scale) + + # Now calculate our boundries + logicalUnitsMM = float(ppiPrinterX) / (logicalScale*25.4) + topLeft, bottomRight = self.margins + left = round(topLeft.x * logicalUnitsMM) + top = round(topLeft.y * logicalUnitsMM) + right = round(dc.DeviceToLogicalYRel(dw) - bottomRight.x * logicalUnitsMM) + bottom = round(dc.DeviceToLogicalYRel(dh) - bottomRight.y * logicalUnitsMM) + self.bounds = (left, top, right-left, bottom-top) + + +#---------------------------------------------------------------------------- + +class ReportFormat(object): + """ + A ReportFormat defines completely how a report is formatted. + + It holds a collection of BlockFormat objects which control the + formatting of individual blocks of the report + + Public instance variables: + + * IncludeImages + Should images from the ListCtrl be printed in the report? + Default: *True* + + * IsColumnHeadingsOnEachPage + Will the column headers be printed at the top of each page? + Default: *True* + + * IsShrinkToFit + Will the columns be shrunk so they all fit into the width of one page? + Default: *False* + + * UseListCtrlTextFormat + If this is True, the text format (i.e. font and text color) of each row will be taken from the ListCtrl, + rather than from the *Cell* format. + Default: *False* + + """ + + def __init__(self): + """ + """ + # Initialize the formats that control the various portions of the report + self.Page = BlockFormat() + self.PageHeader = BlockFormat() + self.ListHeader = BlockFormat() + self.GroupTitle = BlockFormat() + self.ColumnHeader = BlockFormat() + self.Row = BlockFormat() + self.ListFooter = BlockFormat() + self.PageFooter = BlockFormat() + self.Watermark = BlockFormat() + + self.IncludeImages = True + self.IsColumnHeadingsOnEachPage = False + self.IsShrinkToFit = False + self.UseListCtrlTextFormat = True + + # Initialize the watermark format to default values + self.WatermarkFormat() + + #---------------------------------------------------------------------------- + # Accessing + + def GetNamedFormat(self, name): + """ + Return the format used in to format a block with the given name. + """ + return getattr(self, name) + + #---------------------------------------------------------------------------- + # Commands + + def WatermarkFormat(self, font=None, color=None, angle=30, over=False): + """ + Change the format of the watermark printed on this report. + + The actual text of the water mark is set by `ListCtrlPrinter.Watermark` property. + """ + defaultFaceName = "Stencil" + self.Watermark.Font = font or wx.FFont(96, wx.FONTFAMILY_DEFAULT, 0, defaultFaceName) + self.Watermark.TextColor = color or wx.Colour(204, 204, 204) + self.Watermark.Angle = angle + self.Watermark.Over = over + + #---------------------------------------------------------------------------- + # Standard formats + # These are meant to be illustrative rather than definitive + + @staticmethod + def Minimal(headerFontName="Arial", rowFontName="Times New Roman"): + """ + Return a minimal format for a report + """ + fmt = ReportFormat() + fmt.IsShrinkToFit = False + + fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.PageHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) + fmt.PageHeader.Padding = (0, 0, 0, 12) + + fmt.ListHeader.Font = wx.FFont(18, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.ListHeader.Padding = (0, 12, 0, 12) + fmt.ListHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) + + fmt.GroupTitle.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.GroupTitle.Padding = (0, 12, 0, 12) + fmt.GroupTitle.Line(wx.BOTTOM, wx.BLACK, 1, space=5) + + fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.PageFooter.Line(wx.TOP, wx.BLACK, 1, space=3) + fmt.PageFooter.Padding = (0, 16, 0, 0) + + fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.ColumnHeader.Padding = (0, 12, 0, 12) + fmt.ColumnHeader.CellPadding = 5 + fmt.ColumnHeader.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) + fmt.ColumnHeader.AlwaysCenter = True + + fmt.Row.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=rowFontName) + fmt.Row.CellPadding = 5 + fmt.Row.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) + fmt.Row.CanWrap = True + + return fmt + + @staticmethod + def Normal(headerFontName="Gill Sans", rowFontName="Times New Roman"): + """ + Return a reasonable default format for a report + """ + fmt = ReportFormat() + fmt.IsShrinkToFit = True + + fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.PageHeader.Line(wx.BOTTOM, wx.BLUE, 2, space=5) + fmt.PageHeader.Padding = (0, 0, 0, 12) + + fmt.ListHeader.Font = wx.FFont(26, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.ListHeader.TextColor = wx.WHITE + fmt.ListHeader.Padding = (0, 12, 0, 12) + fmt.ListHeader.TextAlignment = wx.ALIGN_LEFT + fmt.ListHeader.Background(wx.BLUE, wx.WHITE, space=(16, 4, 0, 4)) + + fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.GroupTitle.Line(wx.BOTTOM, wx.BLUE, 4, toColor=wx.WHITE, space=5) + fmt.GroupTitle.Padding = (0, 12, 0, 12) + + fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) + fmt.PageFooter.Background(wx.WHITE, wx.BLUE, space=(0, 4, 0, 4)) + + fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.ColumnHeader.CellPadding = 2 + fmt.ColumnHeader.Background(wx.Colour(192, 192, 192)) + fmt.ColumnHeader.GridPen = wx.Pen(wx.WHITE, 1) + fmt.ColumnHeader.Padding = (0, 0, 0, 12) + fmt.ColumnHeader.AlwaysCenter = True + + fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=rowFontName) + fmt.Row.Line(wx.BOTTOM, pen=wx.Pen(wx.BLUE, 1, wx.DOT), space=3) + fmt.Row.CellPadding = 2 + fmt.Row.CanWrap = True + + return fmt + + @staticmethod + def TooMuch(headerFontName="Chiller", rowFontName="Gill Sans"): + """ + Return a reasonable default format for a report + """ + fmt = ReportFormat() + fmt.IsShrinkToFit = False + + fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.PageHeader.TextColor = wx.WHITE + fmt.PageHeader.Background(wx.GREEN, wx.RED, space=(16, 4, 0, 4)) + fmt.PageHeader.Padding = (0, 0, 0, 12) + + fmt.ListHeader.Font = wx.FFont(24, wx.FONTFAMILY_DECORATIVE, face=headerFontName) + fmt.ListHeader.TextColor = wx.WHITE + fmt.ListHeader.Padding = (0, 12, 0, 12) + fmt.ListHeader.TextAlignment = wx.ALIGN_CENTER + fmt.ListHeader.Background(wx.RED, wx.GREEN, space=(16, 4, 0, 4)) + + fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.GroupTitle.TextColor = wx.BLUE + fmt.GroupTitle.Padding = (0, 12, 0, 12) + fmt.GroupTitle.Line(wx.BOTTOM, wx.GREEN, 4, toColor=wx.WHITE, space=5) + + fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DECORATIVE, face=headerFontName) + fmt.PageFooter.Line(wx.TOP, wx.GREEN, 2, toColor=wx.RED, space=3) + fmt.PageFooter.Padding = (0, 16, 0, 0) + + fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) + fmt.ColumnHeader.Background(wx.Colour(255, 215, 0)) + fmt.ColumnHeader.CellPadding = 5 + fmt.ColumnHeader.GridPen = wx.Pen(wx.Colour(192, 192, 192), 1) + + fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_SWISS, face=rowFontName) + fmt.Row.CellPadding = 5 + fmt.Row.GridPen = wx.Pen(wx.BLUE, 1, wx.DOT) + fmt.Row.CanWrap = True + + fmt.Watermark.TextColor = wx.Colour(233, 150, 122) + + return fmt + +#---------------------------------------------------------------------------- + +class BlockFormat(object): + """ + A block format defines how a Block is formatted. + + These properties control the formatting of the matching Block: + + * CanWrap + If the text for this block cannot fit horizontally, should be wrap to a new line (True) + or should it be truncated (False)? + * Font + What font should be used to draw the text of this block + * Padding + How much padding should be applied to the block before the text or other decorations + are drawn? This can be a numeric (which will be applied to all sides) or it can be + a collection of the paddings to be applied to the various sides: (left, top, right, bottom). + * TextAlignment + How should text be aligned within this block? Can be wx.ALIGN_LEFT, wx.ALIGN_CENTER, or + wx.ALIGN_RIGHT. + * TextColor + In what color should be text be drawn? + + The blocks that are based on cells (PageHeader, ColumnHeader, Row, PageFooter) can also + have the following properties set: + + * AlwaysCenter + Will the text in the cells be center aligned, regardless of other settings? + * CellPadding + How much padding should be applied to this cell before the text or other decorations + are drawn? This can be a numeric (which will be applied to all sides) or it can be a + collection of the paddings to be applied to the various sides: (left, top, right, + bottom). + * GridPen + What Pen will be used to draw the grid lines of the cells? + + In addition to these properties, there are some methods which add various decorations to + the blocks: + + * Background(color=wx.BLUE, toColor=None, space=0) + + This gives the block a solid color background (or a gradient background if *toColor* + is not None). If *space* is not 0, *space* pixels will be subtracted from all sides + from the space available to the block. + + * Frame(pen=None, space=0) + + Draw a rectangle around the block in the given pen + + * Line(side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None) + + Draw a line on a given side of the block. If a pen is given, that is used to draw the + line (and the other parameters are ignored), otherwise a solid line (or a gradient + line is *toColor* is not None) of *width* pixels is drawn. + + """ + + def __init__(self): + """ + """ + self.padding = None + self.decorations = list() + self.font = wx.FFont(11, wx.FONTFAMILY_SWISS, face="Gill Sans") + self.textColor = None + self.textAlignment = wx.ALIGN_LEFT + self.alwaysCenter = False + self.canWrap = False + + #THINK: These attributes are only for grids. Should we have a GridBlockFormat object? + self.cellPadding = None + self.gridPen = None + + #---------------------------------------------------------------------------- + # Accessing + + def GetFont(self): + """ + Return the font used by this format + """ + return self.font + + def SetFont(self, font): + """ + Set the font used by this format + """ + self.font = font + + def GetTextAlignment(self): + """ + Return the alignment of text in this format + """ + return self.textAlignment + + def SetTextAlignment(self, alignment): + """ + Set the alignment of text in this format + """ + self.textAlignment = alignment + + def GetTextColor(self): + """ + Return the color of text in this format + """ + return self.textColor + + def SetTextColor(self, color): + """ + Set the color of text in this format + """ + self.textColor = color + + def GetPadding(self): + """ + Get the padding around this format + """ + return self.padding + + def SetPadding(self, padding): + """ + Set the padding around this format + + Padding is either a single numeric (indicating the values on all sides) + or a collection of paddings [left, top, right, bottom] + """ + self.padding = self._MakePadding(padding) + + def GetCellPadding(self): + """ + Get the padding around cells in this format + """ + return self.cellPadding + + def SetCellPadding(self, padding): + """ + Set the padding around cells in this format + + Padding is either a single numeric (indicating the values on all sides) + or a collection of paddings [left, top, right, bottom] + """ + self.cellPadding = self._MakePadding(padding) + + def GetGridPen(self): + """ + Return the pen used to draw a grid in this format + """ + return self.gridPen + + def SetGridPen(self, pen): + """ + Set the pen used to draw a grid in this format + """ + self.gridPen = pen + if self.gridPen: + # Other styles don't produce nice joins + self.gridPen.SetCap(wx.CAP_BUTT) + self.gridPen.SetJoin(wx.JOIN_MITER) + + def _MakePadding(self, padding): + try: + if len(padding) < 4: + return (tuple(padding) + (0, 0, 0, 0))[:4] + else: + return padding + except TypeError: + return (padding,) * 4 + + def GetAlwaysCenter(self): + """ + Return if the text controlled by this format should always be centered? + """ + return self.alwaysCenter + + def SetAlwaysCenter(self, value): + """ + Remember if the text controlled by this format should always be centered? + """ + self.alwaysCenter = value + + def GetCanWrap(self): + """ + Return if the text controlled by this format can wrap to cover more than one line? + """ + return self.canWrap + + def SetCanWrap(self, value): + """ + Remember if the text controlled by this format can wrap to cover more than one line? + """ + self.canWrap = value + + Font = property(GetFont, SetFont) + Padding = property(GetPadding, SetPadding) + TextAlignment = property(GetTextAlignment, SetTextAlignment) + TextColor = property(GetTextColor, SetTextColor) + CellPadding = property(GetCellPadding, SetCellPadding) + GridPen = property(GetGridPen, SetGridPen) + AlwaysCenter = property(GetAlwaysCenter, SetAlwaysCenter) + CanWrap = property(GetCanWrap, SetCanWrap) + + # Misspellers of the world Untie! + # Ok, ok... there're not really misspellings - just alternatives :) + TextAlign = property(GetTextAlignment, SetTextAlignment) + TextColour = property(GetTextColor, SetTextColor) + + #---------------------------------------------------------------------------- + # Calculations + + def CalculateCellPadding(self): + """ + Return a four-tuple that indicates pixel padding (left, top, right, bottom) + around cells in this format + """ + if self.CellPadding: + cellPadding = list(self.CellPadding) + else: + cellPadding = 0, 0, 0, 0 + + if self.GridPen: + penFactor = int((self.GridPen.GetWidth()+1)/2) + cellPadding = [x + penFactor for x in cellPadding] + + return cellPadding + + #---------------------------------------------------------------------------- + # Decorations + + def Add(self, decoration): + """ + Add the given decoration to those adorning blocks with this format + """ + self.decorations.append(decoration) + + + def Line(self, side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None): + """ + Add a line to our decorations. + If a pen is given, we use a straight Line decoration, otherwise we use a + coloured rectangle + """ + if pen: + self.Add(LineDecoration(side, pen, space)) + else: + self.Add(RectangleDecoration(side, None, color, toColor, width, space)) + + + def Background(self, color=wx.BLUE, toColor=None, space=0): + """ + Add a coloured background to the block + """ + self.Add(RectangleDecoration(color=color, toColor=toColor, space=space)) + + + def Frame(self, pen=None, space=0): + """ + Add a rectangle around this block + """ + self.Add(RectangleDecoration(pen=pen, space=space)) + + #---------------------------------------------------------------------------- + # Commands + + def SubtractPadding(self, bounds): + """ + Subtract any padding used by this format from the given bounds + """ + if self.padding is None: + return bounds + else: + return RectUtils.InsetRect(bounds, self.padding) + + + def SubtractDecorations(self, dc, bounds): + """ + Subtract any space used by our decorations from the given bounds + """ + for x in self.decorations: + bounds = x.SubtractFrom(dc, bounds) + return bounds + + + def DrawDecorations(self, dc, bounds, block, over): + """ + Draw our decorations on the given block + """ + for x in self.decorations: + if x.IsDrawOver() == over: + x.DrawDecoration(dc, bounds, block) + + +#---------------------------------------------------------------------------- + +class Block(object): + """ + A Block is a portion of a report that will stack vertically with other + Block. A report consists of several Blocks. + """ + + def __init__(self, engine=None): + self.engine = engine # This is also set when the block is added to a print engine + + #---------------------------------------------------------------------------- + # Accessing + + def GetFont(self): + """ + Return Font that should be used to draw text in this block + """ + return self.GetFormat().GetFont() + + + def GetTextColor(self): + """ + Return Colour that should be used to draw text in this block + """ + return self.GetFormat().GetTextColor() + + + def GetFormat(self): + """ + Return the BlockFormat object that controls the formatting of this block + """ + return self.engine.GetNamedFormat(self.__class__.__name__[:-5]) + + + def GetReducedBlockBounds(self, dc, bounds=None): + """ + Return the bounds of this block once padding and decoration have taken their toll. + """ + bounds = bounds or list(self.GetWorkBounds()) + fmt = self.GetFormat() + bounds = fmt.SubtractPadding(bounds) + bounds = fmt.SubtractDecorations(dc, bounds) + return bounds + + + def GetWorkBounds(self): + """ + Return the boundaries of the work area for this block + """ + return self.engine.workBounds + + + def IsColumnHeadingsOnEachPage(self): + """ + Should the column headers be report at the top of each new page? + """ + return self.engine.reportFormat.IsColumnHeadingsOnEachPage + + + def IncludeImages(self): + """ + Should the images from the ListCtrl be printed in the report? + """ + return self.engine.reportFormat.IncludeImages + + + def IsShrinkToFit(self): + """ + Should row blocks be shrunk to fit within the width of a page? + """ + return self.engine.reportFormat.IsShrinkToFit + + + def IsUseSubstitution(self): + """ + Should the text values printed by this block have substitutions performed before being printed? + + This allows, for example, footers to have '%(currentPage)d of %(totalPages)d' + """ + return True + + + #---------------------------------------------------------------------------- + # Calculating + + def CalculateExtrasHeight(self, dc): + """ + Return the height of the padding and decorations themselves + """ + return 0 - RectUtils.Height(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) + + + def CalculateExtrasWidth(self, dc): + """ + Return the width of the padding and decorations themselves + """ + return 0 - RectUtils.Width(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) + + + def CalculateHeight(self, dc): + """ + Return the heights of this block in pixels + """ + return -1 + + + def CalculateTextHeight(self, dc, txt, bounds=None, font=None): + """ + Return the height of the given txt in pixels + """ + bounds = bounds or self.GetReducedBlockBounds(dc) + font = font or self.GetFont() + dc.SetFont(font) + if self.GetFormat().CanWrap: + return WordWrapRenderer.CalculateHeight(dc, txt, RectUtils.Width(bounds)) + else: + # Calculate the height of one line. The 1000 pixel width + # ensures that 'Wy' doesn't wrap, which might happen if bounds is narrow + return WordWrapRenderer.CalculateHeight(dc, "Wy", 1000) + + + def CanFit(self, height): + """ + Can this block fit into the remaining work area on the page? + """ + return height <= RectUtils.Height(self.GetWorkBounds()) + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + if not self.ShouldPrint(): + return True + + bounds = self.CalculateBounds(dc) + if not self.CanFit(RectUtils.Height(bounds)): + return False + + if self.engine.shouldDrawBlocks: + self.PreDraw(dc, bounds) + self.Draw(dc, bounds) + self.PostDraw(dc, bounds) + + self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) + return True + + + def ShouldPrint(self): + """ + Should this block be printed? + """ + # If this block does not have a format, it is simply skipped + return self.GetFormat() is not None + + + def CalculateBounds(self, dc): + """ + Calculate the bounds of this block + """ + height = self.CalculateHeight(dc) + bounds = list(self.GetWorkBounds()) + bounds = RectUtils.SetHeight(bounds, height) + return bounds + + + def ChangeWorkBoundsBy(self, amt): + """ + Move the top of our work bounds down by the given amount + """ + RectUtils.MoveTopBy(self.engine.workBounds, amt) + + + def Draw(self, dc, bounds): + """ + Draw this block and its decorations allowing for any padding + """ + fmt = self.GetFormat() + decorationBounds = fmt.SubtractPadding(bounds) + fmt.DrawDecorations(dc, decorationBounds, self, False) + textBounds = fmt.SubtractDecorations(dc, list(decorationBounds)) + self.DrawSelf(dc, textBounds) + fmt.DrawDecorations(dc, decorationBounds, self, True) + + + def PreDraw(self, dc, bounds): + """ + Executed before any drawing is done + """ + pass + + + def DrawSelf(self, dc, bounds): + """ + Do the actual work of rendering this block. + """ + pass + + + def PostDraw(self, dc, bounds): + """ + Executed after drawing has completed + """ + pass + + + def DrawText(self, dc, txt, bounds, font=None, alignment=wx.ALIGN_LEFT, valignment=wx.ALIGN_CENTRE, + image=None, color=None, canWrap=True, imageIndex=-1, listCtrl=None): + """ + Draw the given text in the given DC according to the given characteristics. + + This is the workhorse text drawing method for our reporting engine. + + The *font*, *alignment*, and *color* attributes are applied to the drawn text. + + If *image* is not None, it will be drawn to the left of the text. All text is indented + by the width of the image, even if the text is multi-line. + + If *imageIndex* is 0 or more and *listCtrl* is not None, the corresponding image from + the ListCtrl's small image list will be drawn to the left of the text. + + If *canWrap* is True, the text will be wrapped to fit within the given bounds. If it is False, + then the first line of *txt* will be truncated at the edge of the given *bounds*. + """ + GAP_BETWEEN_IMAGE_AND_TEXT = 4 + + def _CalcBitmapPosition(r, height): + if valignment == wx.ALIGN_TOP: + return RectUtils.Top(r) + elif valignment == wx.ALIGN_CENTER: + return RectUtils.CenterY(r) - height / 2 + elif valignment == wx.ALIGN_BOTTOM: + return RectUtils.Bottom(r) - height + else: + return RectUtils.Top(r) + + # Draw any image + if image: + y = _CalcBitmapPosition(bounds, image.Height) + dc.DrawBitmap(image, RectUtils.Left(bounds), y) + RectUtils.MoveLeftBy(bounds, image.GetWidth()+GAP_BETWEEN_IMAGE_AND_TEXT) + elif listCtrl and imageIndex >=0: + imageList = listCtrl.GetImageList(wx.IMAGE_LIST_SMALL) + y = _CalcBitmapPosition(bounds, imageList.GetSize(0)[1]) + imageList.Draw(imageIndex, dc, RectUtils.Left(bounds), y, wx.IMAGELIST_DRAW_TRANSPARENT) + RectUtils.MoveLeftBy(bounds, imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT) + + # Draw the text + dc.SetFont(font or self.GetFont()) + dc.SetTextForeground(color or self.GetTextColor() or wx.BLACK) + if canWrap: + WordWrapRenderer.DrawString(dc, txt, bounds, alignment, valignment) + else: + WordWrapRenderer.DrawTruncatedString(dc, txt, bounds, alignment, valignment) + + + def PerformSubstitutions(self, strings): + """ + Substituted % markers in the given collection of strings. + """ + info = self.engine.GetSubstitutionInfo() + try: + # 'strings' could be a single string or a list of strings + try: + return strings % info + except TypeError: + return [x % info for x in strings] + except ValueError: + # We don't die if we get a substitution error - we just ignore it + return strings + +#---------------------------------------------------------------------------- + +class TextBlock(Block): + """ + A TextBlock prints a string objects. + """ + + def ShouldPrint(self): + """ + Should this block be printed? + """ + # If the block has no text, it should not be printed + if self.GetText(): + return Block.ShouldPrint(self) + else: + return False + + + def GetText(self): + return "Missing GetText() in class %s" % self.__class__.__name__ + + + def GetSubstitutedText(self): + """ + Return the text for this block after all markers have been substituted + """ + if self.IsUseSubstitution(): + return self.PerformSubstitutions(self.GetText()) + else: + return self.GetText() + + + def CalculateHeight(self, dc): + """ + Return the heights of this block in pixels + """ + textHeight = self.CalculateTextHeight(dc, self.GetSubstitutedText()) + extras = self.CalculateExtrasHeight(dc) + return textHeight + extras + + + def DrawSelf(self, dc, bounds): + """ + Do the actual work of rendering this block. + """ + fmt = self.GetFormat() + self.DrawText(dc, self.GetSubstitutedText(), bounds, canWrap=fmt.CanWrap, alignment=fmt.TextAlignment) + + +#---------------------------------------------------------------------------- + +class CellBlock(Block): + """ + A CellBlock is a Block whose data is presented in a cell format. + """ + + def __init__(self): + self.scale = 1 + self.oldScale = 1 + + #---------------------------------------------------------------------------- + # Accessing - Subclasses should override + + def GetCellWidths(self): + """ + Return a list of the widths of the cells in this block. + """ + return list() + + def GetTexts(self): + """ + Return a list of the texts that should be drawn with the cells + """ + return list() + + def GetAlignments(self): + """ + Return a list indicating how the text within each cell is aligned. + """ + return list() + + def GetImages(self): + """ + Return a list of the images that should be drawn in each cell + """ + return list() + + + #---------------------------------------------------------------------------- + # Accessing + + + def GetCombinedLists(self): + """ + Return a collection of Buckets that hold all the values of the + subclass-overridable collections above + """ + buckets = [Bucket(cellWidth=x, text="", align=None, image=None) for x in self.GetCellWidths()] + for (i, x) in enumerate(self.GetSubstitutedTexts()): + buckets[i].text = x + for (i, x) in enumerate(self.GetAlignments()): + buckets[i].align = x + + if self.IncludeImages(): + for (i, x) in enumerate(self.GetImages()): + buckets[i].image = x + + # Calculate where the cell contents should be drawn + cellPadding = self.GetFormat().CalculateCellPadding() + for x in buckets: + x.innerCellWidth = max(0, x.cellWidth - (cellPadding[0] + cellPadding[2])) + + return buckets + + + def GetListCtrl(self): + """ + Return the ListCtrl that is behind this cell block. + """ + return None + + + def GetSubstitutedTexts(self): + """ + Return a list of the texts that should be drawn with the cells + """ + if self.IsUseSubstitution(): + return self.PerformSubstitutions(self.GetTexts()) + else: + return self.GetTexts() + + + #---------------------------------------------------------------------------- + # Calculating + + def CalculateHeight(self, dc): + """ + Return the heights of this block in pixels + """ + GAP_BETWEEN_IMAGE_AND_TEXT = 4 + + # If cells can wrap, figure out the tallest, otherwise we just figure out the height of one line + if self.GetFormat().CanWrap: + font = self.GetFont() + height = 0 + for x in self.GetCombinedLists(): + textWidth = x.innerCellWidth + if self.GetListCtrl() and x.image != -1: + imageList = self.GetListCtrl().GetImageList(wx.IMAGE_LIST_SMALL) + textWidth -= imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT + bounds = [0, 0, textWidth, 99999] + height = max(height, self.CalculateTextHeight(dc, x.text, bounds, font)) + else: + height = self.CalculateTextHeight(dc, "Wy") + + # We also have to allow for cell padding, on top of the normal padding and decorations + cellPadding = self.GetFormat().CalculateCellPadding() + return height + cellPadding[1] + cellPadding[3] + self.CalculateExtrasHeight(dc) + + + def CalculateWidth(self, dc): + """ + Calculate the total width of this block (cells plus padding) + """ + return sum(x.cellWidth for x in self.GetCombinedLists()) + self.CalculateExtrasWidth(dc) + + #---------------------------------------------------------------------------- + # Commands + + def CalculateBounds(self, dc): + """ + Calculate the bounds of this block + """ + height = self.CalculateHeight(dc) + bounds = list(self.GetWorkBounds()) + bounds = RectUtils.SetHeight(bounds, height) + bounds = RectUtils.SetWidth(bounds, self.CalculateWidth(dc)) + bounds = RectUtils.MultiplyOrigin(bounds, 1 / self.scale) + return bounds + + #def CanFit(self, height): + # height *= self.scale + # return Block.CanFit(self, height) + + def ChangeWorkBoundsBy(self, height): + """ + Change our workbounds by our scaled height + """ + Block.ChangeWorkBoundsBy(self, height * self.scale) + + + def PreDraw(self, dc, bounds): + """ + Apply our scale before performing any drawing + """ + self.oldScale = dc.GetUserScale() + dc.SetUserScale(self.scale * self.oldScale[0], self.scale * self.oldScale[1]) + + + def PostDraw(self, dc, bounds): + """ + Restore the scaling to what it used to be + """ + dc.SetUserScale(*self.oldScale) + + + def DrawSelf(self, dc, bounds): + """ + Do the actual work of rendering this block. + """ + cellFmt = self.GetFormat() + cellPadding = cellFmt.CalculateCellPadding() + combined = self.GetCombinedLists() + + # Calculate cell boundaries + cell = list(bounds) + RectUtils.SetWidth(cell, 0) + for x in combined: + RectUtils.SetLeft(cell, RectUtils.Right(cell)) + RectUtils.SetWidth(cell, x.cellWidth) + x.cell = list(cell) + + # Draw each cell + font = self.GetFont() + for x in combined: + cellBounds = RectUtils.InsetRect(x.cell, cellPadding) + self.DrawText(dc, x.text, cellBounds, font, x.align, imageIndex=x.image, + canWrap=cellFmt.CanWrap, listCtrl=self.GetListCtrl()) + + if cellFmt.GridPen and combined: + dc.SetPen(cellFmt.GridPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + top = RectUtils.Top(bounds) + bottom = RectUtils.Bottom(bounds) + + # Draw the interior dividers + for x in combined[:-1]: + right = RectUtils.Right(x.cell) + dc.DrawLine(right, top, right, bottom) + + # Draw the surrounding frame + left = RectUtils.Left(combined[0].cell) + right = RectUtils.Right(combined[-1].cell) + dc.DrawRectangle(left, top, right-left, bottom-top) + + +#---------------------------------------------------------------------------- + +class ThreeCellBlock(CellBlock): + """ + A ThreeCellBlock divides its space evenly into three cells. + """ + + def GetCellWidths(self): + """ + Return a list of the widths of the cells in this block + """ + # We divide the available space between the non-empty cells + numNonEmptyTexts = sum(1 for x in self.GetTexts() if x) + if not numNonEmptyTexts: + return (0, 0, 0) + + widths = list() + width = round(RectUtils.Width(self.GetWorkBounds()) / numNonEmptyTexts) + for x in self.GetTexts(): + if x: + widths.append(width) + else: + widths.append(0) + return widths + + + def GetAlignments(self): + """ + Return a list indicating how the text within each cell is aligned. + """ + return (wx.ALIGN_LEFT, wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_RIGHT) + +#---------------------------------------------------------------------------- + +class ReportBlock(Block): + """ + A ReportBlock is boot strap Block that represents an entire report. + """ + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + if not self.engine.listCtrls: + return True + + # Print each ListView. Each list but the first starts on a separate page + self.engine.AddBlock(ListBlock(*self.engine.listCtrls[0])) + for (lv, title) in self.engine.listCtrls[1:]: + self.engine.AddBlock(PageBreakBlock()) + self.engine.AddBlock(ListBlock(lv, title)) + + return True + + +#---------------------------------------------------------------------------- + +class PageHeaderBlock(ThreeCellBlock): + """ + A PageHeaderBlock appears at the top of every page. + """ + + def GetTexts(self): + """ + Return the array of texts to be printed in the cells + """ + return self.engine.pageHeader + + + +#---------------------------------------------------------------------------- + +class PageFooterBlock(ThreeCellBlock): + """ + A PageFooterBlock appears at the bottom of every page. + """ + + def GetTexts(self): + """ + Return the array of texts to be printed in the cells + """ + return self.engine.pageFooter + + + #---------------------------------------------------------------------------- + # Printing + + + def CalculateBounds(self, dc): + """ + Calculate the bounds of this block + """ + # Draw the footer at the bottom of the page + height = self.CalculateHeight(dc) + bounds = list(self.GetWorkBounds()) + return [RectUtils.Left(bounds), RectUtils.Bottom(bounds) - height, + RectUtils.Width(bounds), height] + + + def ChangeWorkBoundsBy(self, height): + """ + The footer changes the bottom of the work bounds + """ + RectUtils.MoveBottomBy(self.engine.workBounds, -height) + + +#---------------------------------------------------------------------------- + +class PageBreakBlock(Block): + """ + A PageBreakBlock acts a page break. + """ + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + + # Completely fill the remaining area on the page, forcing a page break + bounds = self.GetWorkBounds() + self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) + + return True + +#---------------------------------------------------------------------------- + +class RunningBlockPusher(Block): + """ + A RunningBlockPusher pushes (or pops) a running block onto the stack when it is executed. + """ + + def __init__(self, block, push=True): + """ + """ + self.block = block + self.push = push + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + if self.push: + self.engine.AddRunningBlock(self.block) + else: + self.engine.RemoveRunningBlock(self.block) + + return True + +#---------------------------------------------------------------------------- + +class ListBlock(Block): + """ + A ListBlock handles the printing of an entire ListCtrl. + """ + + def __init__(self, lv, title): + self.lv = lv + self.title = title + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + + cellWidths = self.CalculateCellWidths() + boundsWidth = RectUtils.Width(self.GetWorkBounds()) + + # Break the list into vertical slices. Each one but the first will be placed on a + # new page. + first = True + for (left, right) in self.CalculateSlices(boundsWidth, cellWidths): + if not first: + self.engine.AddBlock(PageBreakBlock()) + self.engine.AddBlock(ListHeaderBlock(self.lv, self.title)) + self.engine.AddBlock(ListSliceBlock(self.lv, left, right, cellWidths)) + self.engine.AddBlock(ListFooterBlock(self.lv, "")) + first = False + + return True + + + def CalculateCellWidths(self): + """ + Return a list of the widths of the cells in this lists + """ + columnHeaderFmt = self.engine.GetNamedFormat("ColumnHeader") + cellPadding = columnHeaderFmt.CalculateCellPadding() + padding = cellPadding[0] + cellPadding[2] + return [self.lv.GetColumnWidth(i) + padding for i in range(self.lv.GetColumnCount())] + + + def CalculateSlices(self, maxWidth, columnWidths): + """ + Return a list of integer pairs, where each pair represents + the left and right columns that can fit into the width of one page + """ + firstColumn = 0 + + # If a GroupListView has a column just for the expand/collapse, don't include it + if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: + firstColumn = 1 + + # If we are shrinking to fit or all the columns fit, just return all columns + if self.IsShrinkToFit() or (sum(columnWidths)) <= maxWidth: + return [ [firstColumn, len(columnWidths)-1] ] + + pairs = list() + left = firstColumn + right = firstColumn + while right < len(columnWidths): + if (sum(columnWidths[left:right+1])) > maxWidth: + if left == right: + pairs.append([left, right]) + left += 1 + right += 1 + else: + pairs.append([left, right-1]) + left = right + else: + right += 1 + + if left < len(columnWidths): + pairs.append([left, right-1]) + + return pairs + + +#---------------------------------------------------------------------------- + +class ListHeaderBlock(TextBlock): + """ + A ListHeaderBlock is the title that comes before an ListCtrl. + """ + + def __init__(self, lv, title): + self.lv = lv + self.title = title + + def GetText(self): + """ + Return the text that will be printed in this block + """ + return self.title + +#---------------------------------------------------------------------------- + +class ListFooterBlock(TextBlock): + """ + A ListFooterBlock is the text that comes after an ListCtrl. + """ + + def __init__(self, lv, text): + self.lv = lv + self.text = text + + def GetText(self): + """ + Return the text that will be printed in this block + """ + return self.text + + +#---------------------------------------------------------------------------- + +class GroupTitleBlock(TextBlock): + """ + A GroupTitleBlock is the title that comes before a list group. + """ + + def __init__(self, lv, group): + self.lv = lv + self.group = group + + def GetText(self): + """ + Return the text that will be printed in this block + """ + return self.group.title + +#---------------------------------------------------------------------------- + +class ListSliceBlock(Block): + """ + A ListSliceBlock prints a vertical slice of an ListCtrl. + """ + + def __init__(self, lv, left, right, allCellWidths): + self.lv = lv + self.left = left + self.right = right + self.allCellWidths = allCellWidths + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + # If we are shrinking our cells, calculate the shrink factor + if self.IsShrinkToFit(): + scale = self.CalculateShrinkToFit(dc) + else: + scale = 1 + + headerBlock = ColumnHeaderBlock(self.lv, self.left, self.right, scale, self.allCellWidths) + self.engine.AddBlock(headerBlock) + + if self.IsColumnHeadingsOnEachPage(): + self.engine.AddBlock(RunningBlockPusher(headerBlock)) + + # Are we printing a GroupListView? + # We can't use isinstance() since ObjectListView may not be installed + if hasattr(self.lv, "GetShowGroups") and self.lv.GetShowGroups(): + self.engine.AddBlock(GroupListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) + else: + self.engine.AddBlock(ListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) + + if self.IsColumnHeadingsOnEachPage(): + self.engine.AddBlock(RunningBlockPusher(headerBlock, False)) + + return True + + def CalculateShrinkToFit(self, dc): + """ + How much do we have to shrink our rows by to fit onto the page? + """ + block = ColumnHeaderBlock(self.lv, self.left, self.right, 1, self.allCellWidths) + block.engine = self.engine + rowWidth = block.CalculateWidth(dc) + boundsWidth = RectUtils.Width(self.GetWorkBounds()) + + if rowWidth > boundsWidth: + return float(boundsWidth) / float(rowWidth) + else: + return 1 + +#---------------------------------------------------------------------------- + +class ColumnBasedBlock(CellBlock): + """ + A ColumnBasedBlock is a cell block that takes its width from the + columns of a ListCtrl. + + This is an abstract class + """ + + def __init__(self, lv, left, right, scale, allCellWidths): + self.lv = lv + self.left = left + self.right = right + self.scale = scale + self.allCellWidths = allCellWidths + + #---------------------------------------------------------------------------- + # Accessing - Subclasses should override + + def GetListCtrl(self): + """ + Return the ListCtrl that is behind this cell block. + """ + return self.lv + + + def GetCellWidths(self): + """ + Return the widths of the cells used in this block + """ + #return [self.allCellWidths[i] for i in range(self.left, self.right+1)] + return self.allCellWidths[self.left:self.right+1] + + #---------------------------------------------------------------------------- + # Utiltities + + def GetColumnAlignments(self, lv, left, right): + """ + Return the alignments of the given slice of columns + """ + listAlignments = [lv.GetColumn(i).GetAlign() for i in range(left, right+1)] + mapping = { + wx.LIST_FORMAT_LEFT: wx.ALIGN_LEFT, + wx.LIST_FORMAT_RIGHT: wx.ALIGN_RIGHT, + wx.LIST_FORMAT_CENTRE: wx.ALIGN_CENTRE, + } + return [mapping[x] for x in listAlignments] + + + +#---------------------------------------------------------------------------- + +class ColumnHeaderBlock(ColumnBasedBlock): + """ + A ColumnHeaderBlock prints a portion of the columns header in a ListCtrl. + """ + + #---------------------------------------------------------------------------- + # Accessing + + def GetTexts(self): + """ + Return a list of the texts that should be drawn with the cells + """ + return [self.lv.GetColumn(i).GetText() for i in range(self.left, self.right+1)] + + def GetAlignments(self): + """ + Return a list indicating how the text within each cell is aligned. + """ + if self.GetFormat().AlwaysCenter: + return [wx.ALIGN_CENTRE for i in range(self.left, self.right+1)] + else: + return self.GetColumnAlignments(self.lv, self.left, self.right) + + def GetImages(self): + """ + Return a list of the images that should be drawn in each cell + """ + # For some reason, columns return 0 when they have no image, rather than -1 like they should + images = list() + for i in range(self.left, self.right+1): + imageIndex = self.lv.GetColumn(i).GetImage() + if imageIndex == 0: + imageIndex = -1 + images.append(imageIndex) + return images + + + def IsUseSubstitution(self): + """ + Should the text values printed by this block have substitutions performed before being printed? + + Normally, we don't want to substitute within values that comes from the ListCtrl. + """ + return False + + +#---------------------------------------------------------------------------- + +class ListRowsBlock(Block): + """ + A ListRowsBlock prints rows of an ListCtrl. + """ + + def __init__(self, lv, left, right, scale, allCellWidths): + """ + """ + self.lv = lv + self.left = left + self.right = right + self.scale = scale + self.allCellWidths = allCellWidths + self.currentIndex = 0 + self.totalRows = self.lv.GetItemCount() + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + # This block works by printing a single row and then rescheduling itself + # to print the remaining rows after the current row has finished. + + if self.currentIndex < self.totalRows: + self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) + self.currentIndex += 1 + self.engine.AddBlock(self) + + return True + +#---------------------------------------------------------------------------- + +class GroupListRowsBlock(Block): + """ + A GroupListRowsBlock prints rows of an GroupListView. + """ + + def __init__(self, lv, left, right, scale, allCellWidths): + """ + """ + self.lv = lv # Must be a GroupListView + self.left = left + self.right = right + self.scale = scale + self.allCellWidths = allCellWidths + + self.currentIndex = 0 + self.totalRows = self.lv.GetItemCount() + + #---------------------------------------------------------------------------- + # Commands + + def Print(self, dc): + """ + Print this Block. + + Return True if the Block has finished printing + """ + # This block works by printing a single row and then rescheduling itself + # to print the remaining rows after the current row has finished. + + if self.currentIndex >= self.totalRows: + return True + + # If GetObjectAt() return an object, then it's a normal row. + # Otherwise, if the innerList object isn't None, it must be a ListGroup + # We can't use isinstance(x, GroupListView) because ObjectListView may not be installed + if self.lv.GetObjectAt(self.currentIndex): + self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) + elif self.lv.innerList[self.currentIndex]: + self.engine.AddBlock(GroupTitleBlock(self.lv, self.lv.innerList[self.currentIndex])) + + # Schedule the printing of the remaining rows + self.currentIndex += 1 + self.engine.AddBlock(self) + + return True + + +#---------------------------------------------------------------------------- + +class RowBlock(ColumnBasedBlock): + """ + A RowBlock prints a vertical slice of a single row of an ListCtrl. + """ + + def __init__(self, lv, rowIndex, left, right, scale, allCellWidths): + self.lv = lv + self.rowIndex = rowIndex + self.left = left + self.right = right + self.scale = scale + self.allCellWidths = allCellWidths + + #---------------------------------------------------------------------------- + # Accessing + + + def GetFont(self): + """ + Return Font that should be used to draw text in this block + """ + font = None + if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): + # Figure out what font is being used for this row in the list. + # Unfortunately, there is no one way to find this information: virtual mode lists + # have to be treated in a different manner from non-virtual lists. + listCtrl = self.GetListCtrl() + if listCtrl.IsVirtual(): + attr = listCtrl.OnGetItemAttr(self.rowIndex) + if attr and attr.HasFont(): + font = attr.GetFont() + else: + font = listCtrl.GetItemFont(self.rowIndex) + + if font and font.IsOk(): + return font + else: + return self.GetFormat().GetFont() + + + def GetTextColor(self): + """ + Return Colour that should be used to draw text in this block + """ + color = None + if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): + # Figure out what text colour is being used for this row in the list. + # Unfortunately, there is no one way to find this information: virtual mode lists + # have to be treated in a different manner from non-virtual lists. + listCtrl = self.GetListCtrl() + if listCtrl.IsVirtual(): + attr = listCtrl.OnGetItemAttr(self.rowIndex) + if attr and attr.HasTextColour(): + color = attr.GetTextColour() + else: + color = listCtrl.GetItemTextColour(self.rowIndex) + + if color and color.IsOk(): + return color + else: + return self.GetFormat().GetTextColor() + + + def IsUseSubstitution(self): + """ + Should the text values printed by this block have substitutions performed before being printed? + + Normally, we don't want to substitute within values that comes from the ListCtrl. + """ + return False + + #---------------------------------------------------------------------------- + # Overrides for CellBlock + + def GetTexts(self): + """ + Return a list of the texts that should be drawn with the cells + """ + return [self.lv.GetItem(self.rowIndex, i).GetText() for i in range(self.left, self.right+1)] + + def GetAlignments(self): + """ + Return a list indicating how the text within each cell is aligned. + """ + return self.GetColumnAlignments(self.lv, self.left, self.right) + + def GetImages(self): + """ + Return a list of the images that should be drawn in each cell + """ + return [self.lv.GetItem(self.rowIndex, i).GetImage() for i in range(self.left, self.right+1)] + + #def GetTexts(self): + # """ + # Return a list of the texts that should be drawn with the cells + # """ + # modelObjects = self.lv.GetObjectAt(self.rowIndex) + # return [self.lv.GetStringValueAt(modelObjects, i) for i in range(self.left, self.right+1)] + # + #def GetAlignments(self): + # """ + # Return a list indicating how the text within each cell is aligned. + # """ + # return [self.lv.columns[i].GetAlignment() for i in range(self.left, self.right+1)] + # + #def GetImages(self): + # """ + # Return a list of the images that should be drawn in each cell + # """ + # modelObjects = self.lv.GetObjectAt(self.rowIndex) + # return [self.lv.GetImageAt(modelObjects, i) for i in range(self.left, self.right+1)] + + +#---------------------------------------------------------------------------- + +class Decoration(object): + """ + A Decoration add some visual effect to a Block (e.g. borders, + background image, watermark). They can also reserve a chunk of their Blocks + space for their own use. + + Decorations are added to a BlockFormat which is then applied to a ReportBlock. + + All the decorations for a block are drawn into the same area. If two decorations are + added, they will draw over the top of each other. This is normally what is expected, + but may sometimes be surprising. For example, if you add two Lines to the left of the + same block, they will draw over the top of each other. + + """ + + #---------------------------------------------------------------------------- + # Accessing + + def IsDrawOver(self): + """ + Return True if this decoration should be drawn over it's block. If not, + it will be drawn underneath + """ + return False + + #---------------------------------------------------------------------------- + # Commands + + def SubtractFrom(self, dc, bounds): + """ + Subtract the space used by this decoration from the given bounds + """ + return bounds + + def DrawDecoration(self, dc, bounds, block): + """ + Draw this decoration + """ + pass + +#---------------------------------------------------------------------------- + +class RectangleDecoration(Decoration): + """ + A RectangleDecoration draw a rectangle around or on the side of a block. + + The rectangle can be hollow, solid filled or gradient-filled. It can have + a frame drawn as well. + + """ + + def __init__(self, side=None, pen=None, color=None, toColor=None, width=0, space=0): + """ + If color is None, the rectangle will be hollow. + If toColor is None, the rectangle will be filled with "color" otherwise it will be + gradient-filled. + If pen is not None, the rectangle will be framed with that pen. + If no side is given, the rectangle will be drawn around the block. In that case, + space can be a list giving the space on each side. + """ + self.side = side + self.pen = pen + self.color = color + self.toColor = toColor + self.width = width + self.space = space + + #---------------------------------------------------------------------------- + # Commands + + def SubtractFrom(self, dc, bounds): + """ + Subtract the space used by this decoration from the given bounds + """ + if self.side is None: + return RectUtils.InsetBy(RectUtils.InsetBy(bounds, self.space), self.width) + + inset = self.space + self.width + if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) + if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) + if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) + if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) + return bounds + + + def DrawDecoration(self, dc, bounds, block): + """ + Draw this decoration + """ + rect = self._CalculateRect(bounds) + + if self.color: + if self.toColor is None: + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.Brush(self.color)) + dc.DrawRectangle(*rect) + else: + dc.GradientFillLinear(wx.Rect(*rect), self.color, self.toColor) + + if self.pen: + dc.SetPen(self.pen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.DrawRectangle(*rect) + + + def _CalculateRect(self, bounds): + """ + Calculate the rectangle that this decoration is going to paint + """ + if self.side is None: + return bounds + if self.side == wx.LEFT: + return (RectUtils.Left(bounds), RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) + if self.side == wx.RIGHT: + return (RectUtils.Right(bounds) - self.width, RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) + if self.side == wx.TOP: + return (RectUtils.Left(bounds), RectUtils.Top(bounds), RectUtils.Width(bounds), self.width) + if self.side == wx.BOTTOM: + return (RectUtils.Left(bounds), RectUtils.Bottom(bounds) - self.width, RectUtils.Width(bounds), self.width) + + return bounds + +#---------------------------------------------------------------------------- + +class LineDecoration(Decoration): + """ + A LineDecoration draws a line on the side of a decoration. + """ + + def __init__(self, side=wx.BOTTOM, pen=None, space=0): + self.side = side + self.pen = pen + self.space = space + + #---------------------------------------------------------------------------- + # Commands + + def SubtractFrom(self, dc, bounds): + """ + Subtract the space used by this decoration from the given bounds + """ + inset = self.space + if self.pen is not None: + inset += self.pen.GetWidth() + + if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) + if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) + if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) + if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) + return bounds + + + def DrawDecoration(self, dc, bounds, block): + """ + Draw this decoration + """ + if self.pen == None: + return + + if self.side == wx.LEFT: + pt1 = RectUtils.TopLeft(bounds) + pt2 = RectUtils.BottomLeft(bounds) + elif self.side == wx.RIGHT: + pt1 = RectUtils.TopRight(bounds) + pt2 = RectUtils.BottomRight(bounds) + elif self.side == wx.TOP: + pt1 = RectUtils.TopLeft(bounds) + pt2 = RectUtils.TopRight(bounds) + elif self.side == wx.BOTTOM: + pt1 = RectUtils.BottomLeft(bounds) + pt2 = RectUtils.BottomRight(bounds) + + dc.SetPen(self.pen) + dc.DrawLine(pt1[0], pt1[1], pt2[0], pt2[1]) + +#---------------------------------------------------------------------------- + +class WatermarkDecoration(Decoration): + """ + A WatermarkDecoration draws an angled line of text over each page. + + The watermark is rotated around the center of the page. + + If *over* is True, the watermark will be printed on top of the page. + Otherwise, it will be printed under the page. + + """ + + def __init__(self, text, font=None, color=None, angle=30, over=True): + """ + """ + self.text = text + self.color = color or wx.Color(128, 128, 128, 128) + self.font = font or wx.FFont(128, wx.SWISS, 0) + self.angle = angle + self.over = over + + def IsDrawOver(self): + """ + Should this decoration be drawn over the rest of page? + """ + return self.over + + def DrawDecoration(self, dc, bounds, block): + """ + Draw the decoration + """ + dc.SetFont(self.font) + dc.SetTextForeground(self.color) + + # Rotate the text around the center of the page + cx, cy = RectUtils.Center(bounds) + w, h = dc.GetTextExtent(self.text) + + x = cx - w/2 + y = cy - h/2 + (w/2 * math.sin(math.radians(self.angle))) + + dc.DrawRotatedText(self.text, x, y, self.angle) + +#---------------------------------------------------------------------------- + +class ImageDecoration(Decoration): + """ + A ImageDecoration draws an image over (or under) the given block. + + NB: Printer contexts do not honor alpha channels. + """ + + def __init__(self, image=None, horizontalAlign=wx.CENTER, verticalAlign=wx.CENTER, over=True): + """ + image must be either an wx.Image or a wx.Bitmap + """ + self.horizontalAlign = horizontalAlign + self.verticalAlign = verticalAlign + self.over = True + + self.bitmap = image + if isinstance(image, wx.Image): + self.bitmap = wx.BitmapFromImage(image) + + def DrawDecoration(self, dc, bounds, block): + """ + Draw the decoration + """ + if not self.bitmap: + return + + if self.horizontalAlign == wx.LEFT: + x = RectUtils.Left(bounds) + elif self.horizontalAlign == wx.RIGHT: + x = RectUtils.Right(bounds) - self.bitmap.Width + else: + x = RectUtils.CenterX(bounds) - self.bitmap.Width/2 + + if self.verticalAlign == wx.TOP: + y = RectUtils.Top(bounds) + elif self.verticalAlign == wx.BOTTOM: + y = RectUtils.Bottom(bounds) - self.bitmap.Height + else: + y = RectUtils.CenterY(bounds) - self.bitmap.Height/2 + + dc.DrawBitmap(self.bitmap, x, y, True) + + +#---------------------------------------------------------------------------- + +class Bucket(object): + """ + General purpose, hold-all data object + """ + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + strs = ["%s=%r" % kv for kv in self.__dict__.items()] + return "Bucket(" + ", ".join(strs) + ")" + +#---------------------------------------------------------------------------- + +class RectUtils: + """ + Static rectangle utilities. + + Rectangles are a list or tuple of 4-elements: [left, top, width, height] + """ + + #---------------------------------------------------------------------------- + # Accessing + + @staticmethod + def Left(r): return r[0] + + @staticmethod + def Top(r): return r[1] + + @staticmethod + def Width(r): return r[2] + + @staticmethod + def Height(r): return r[3] + + @staticmethod + def Right(r): return r[0] + r[2] + + @staticmethod + def Bottom(r): return r[1] + r[3] + + @staticmethod + def TopLeft(r): return [r[0], r[1]] + + @staticmethod + def TopRight(r): return [r[0] + r[2], r[1]] + + @staticmethod + def BottomLeft(r): return [r[0], r[1] + r[3]] + + @staticmethod + def BottomRight(r): return [r[0] + r[2], r[1] + r[3]] + + @staticmethod + def CenterX(r): return r[0] + r[2]/2 + + @staticmethod + def CenterY(r): return r[1] + r[3]/2 + + @staticmethod + def Center(r): return [r[0] + r[2]/2, r[1] + r[3]/2] + + #---------------------------------------------------------------------------- + # Modifying + + @staticmethod + def SetLeft(r, l): + r[0] = l + return r + + @staticmethod + def SetTop(r, t): + r[1] = t + return r + + @staticmethod + def SetWidth(r, w): + r[2] = w + return r + + @staticmethod + def SetHeight(r, h): + r[3] = h + return r + + @staticmethod + def MoveLeftBy(r, delta): + r[0] += delta + r[2] -= delta + return r + + @staticmethod + def MoveTopBy(r, delta): + r[1] += delta + r[3] -= delta + return r + + @staticmethod + def MoveRightBy(r, delta): + r[2] += delta + return r + + @staticmethod + def MoveBottomBy(r, delta): + r[3] += delta + return r + + #---------------------------------------------------------------------------- + # Calculations + + @staticmethod + def InsetBy(r, delta): + if delta is None: + return r + try: + delta[0] # is it indexable? + return RectUtils.InsetRect(r, delta) + except: + return RectUtils.InsetRect(r, (delta, delta, delta, delta)) + + @staticmethod + def InsetRect(r, r2): + if r2 is None: + return r + else: + return [r[0] + r2[0], r[1] + r2[1], r[2] - (r2[0] + r2[2]), r[3] - (r2[1] + r2[3])] + + @staticmethod + def MultiplyOrigin(r, factor): + return [r[0]*factor, r[1]*factor, r[2], r[3]] + +#---------------------------------------------------------------------------- +# TESTING ONLY +#---------------------------------------------------------------------------- + +if __name__ == '__main__': + import wx + from ObjectListView import ObjectListView, FastObjectListView, GroupListView, ColumnDefn + + # Where can we find the Example module? + import sys + sys.path.append("../Examples") + + import ExampleModel + import ExampleImages + + class MyFrame(wx.Frame): + def __init__(self, *args, **kwds): + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + + self.panel = wx.Panel(self, -1) + #self.lv = ObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) + self.lv = GroupListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) + #self.lv = FastObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) + + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.lv, 1, wx.ALL|wx.EXPAND, 4) + self.panel.SetSizer(sizer_2) + self.panel.Layout() + + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_1.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(sizer_1) + self.Layout() + + musicImage = self.lv.AddImages(ExampleImages.getMusic16Bitmap(), ExampleImages.getMusic32Bitmap()) + artistImage = self.lv.AddImages(ExampleImages.getUser16Bitmap(), ExampleImages.getUser32Bitmap()) + + self.lv.SetColumns([ + ColumnDefn("Title", "left", 200, "title", imageGetter=musicImage), + ColumnDefn("Artist", "left", 150, "artist", imageGetter=artistImage), + ColumnDefn("Last Played", "left", 100, "lastPlayed"), + ColumnDefn("Size", "center", 100, "sizeInBytes"), + ColumnDefn("Rating", "center", 100, "rating"), + ]) + + #self.lv.CreateCheckStateColumn() + self.lv.SetSortColumn(self.lv.columns[2]) + self.lv.SetObjects(ExampleModel.GetTracks()) + + wx.CallLater(50, self.run) + + def run(self): + printer = ListCtrlPrinter(self.lv, "Playing with ListCtrl Printing") + printer.ReportFormat = ReportFormat.Normal() + printer.ReportFormat.WatermarkFormat(over=True) + printer.ReportFormat.IsColumnHeadingsOnEachPage = True + + #printer.ReportFormat.Page.Add(ImageDecoration(ExampleImages.getGroup32Bitmap(), wx.RIGHT, wx.BOTTOM)) + + #printer.PageHeader("%(listTitle)s") # nice idea but not possible at the moment + printer.PageHeader = "Playing with ListCtrl Printing" + printer.PageFooter = ("Bright Ideas Software", "%(date)s", "%(currentPage)d of %(totalPages)d") + printer.Watermark = "Sloth!" + + #printer.PageSetup() + printer.PrintPreview(self) + + + app = wx.PySimpleApp(0) + wx.InitAllImageHandlers() + frame_1 = MyFrame(None, -1, "") + app.SetTopWindow(frame_1) + frame_1.Show() + app.MainLoop() diff --git a/odmtools/lib/oldOlv/OLVEvent.py b/odmtools/lib/oldOlv/OLVEvent.py index 1c4c36f..a447cb4 100644 --- a/odmtools/lib/oldOlv/OLVEvent.py +++ b/odmtools/lib/oldOlv/OLVEvent.py @@ -1,279 +1,279 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: OLVEvent.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/18 JPP Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events -# 2008/07/16 JPP Added group-related events -# 2008/06/19 JPP Added EVT_SORT -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: - -""" -The OLVEvent module holds all the events used by the ObjectListView module. -""" - -__author__ = "Phillip Piper" -__date__ = "3 August 2008" -__version__ = "1.1" - -import wx - -#====================================================================== -# Event ids and types - -def _EventMaker(): - evt = wx.NewEventType() - return (evt, wx.PyEventBinder(evt)) - -(olv_EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTING) = _EventMaker() -(olv_EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_STARTED) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHING) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHED, EVT_CELL_EDIT_FINISHED) = _EventMaker() -(olv_EVT_SORT, EVT_SORT) = _EventMaker() -(olv_EVT_GROUP_CREATING, EVT_GROUP_CREATING) = _EventMaker() -(olv_EVT_GROUP_SORT, EVT_GROUP_SORT) = _EventMaker() -(olv_EVT_EXPANDING, EVT_EXPANDING) = _EventMaker() -(olv_EVT_EXPANDED, EVT_EXPANDED) = _EventMaker() -(olv_EVT_COLLAPSING, EVT_COLLAPSING) = _EventMaker() -(olv_EVT_COLLAPSED, EVT_COLLAPSED) = _EventMaker() - -#====================================================================== -# Event parameter blocks - -class VetoableEvent(wx.PyCommandEvent): - """ - Base class for all cancellable actions - """ - - def __init__(self, evtType): - wx.PyCommandEvent.__init__(self, evtType, -1) - self.veto = False - - def Veto(self, isVetoed=True): - """ - Veto (or un-veto) this event - """ - self.veto = isVetoed - - def IsVetoed(self): - """ - Has this event been vetod? - """ - return self.veto - -#---------------------------------------------------------------------------- - -class CellEditEvent(VetoableEvent): - """ - Base class for all cell editing events - """ - - def SetParameters(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor): - self.objectListView = objectListView - self.rowIndex = rowIndex - self.subItemIndex = subItemIndex - self.rowModel = rowModel - self.cellValue = cellValue - self.editor = editor - -#---------------------------------------------------------------------------- - -class CellEditStartedEvent(CellEditEvent): - """ - A cell has started to be edited. - - All attributes are public and should be considered read-only. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - -#---------------------------------------------------------------------------- - -class CellEditStartingEvent(CellEditEvent): - """ - A cell is about to be edited. - - All attributes are public and should be considered read-only. Methods are provided for - information that can be changed. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - self.newEditor = None - self.shouldConfigureEditor = True - - def SetCellBounds(self, rect): - """ - Change where the editor will be placed. - rect is a list: [left, top, width, height] - """ - self.cellBounds = rect - - def SetNewEditor(self, control): - """ - Use the given control instead of the editor. - """ - self.newEditor = control - - def DontConfigureEditor(self): - """ - The editor will not be automatically configured. - - If this is called, the event handler must handle all configuration. In - particular, it must configure its own event handlers to that - ObjectListView.CancelCellEdit() is called when the user presses Escape, - and ObjectListView.CommitCellEdit() is called when the user presses - Enter/Return or when the editor loses focus. """ - self.shouldConfigureEditor = False - -#---------------------------------------------------------------------------- - -class CellEditFinishedEvent(CellEditEvent): - """ - The user has finished editing a cell. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, None, None) - self.userCancelled = userCancelled - -#---------------------------------------------------------------------------- - -class CellEditFinishingEvent(CellEditEvent): - """ - The user is finishing editing a cell. - - If this event is vetoed, the edit will be cancelled silently. This is useful if the - event handler completely handles the model updating. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.userCancelled = userCancelled - - def SetCellValue(self, value): - """ - If the event handler sets the cell value here, this value will be used to update the model - object, rather than the value that was actually in the cell editor - """ - self.cellValue = value - -#---------------------------------------------------------------------------- - -class SortEvent(VetoableEvent): - """ - The user wants to sort the ObjectListView. - - When sortModelObjects is True, the event handler should sort the model objects used by - the given ObjectListView. If the "modelObjects" instance variable is not None, that - collection of objects should be sorted, otherwise the "modelObjects" collection of the - ObjectListView should be sorted. For a VirtualObjectListView, "modelObjects" will - always be None and the programmer must sort the object in whatever backing store is - being used. - - When sortModelObjects is False, the event handler must sort the actual ListItems in - the OLV. It does this by calling SortListItemsBy(), passing a callable that accepts - two model objects as parameters. sortModelObjects must be True for a - VirtualObjectListView (or a FastObjectListView) since virtual lists cannot sort items. - - If the handler calls Veto(), no further default processing will be done. - If the handler calls Handled(), default processing concerned with UI will be done. This - includes updating sort indicators. - If the handler calls neither of these, all default processing will be done. - """ - def __init__(self, objectListView, sortColumnIndex, sortAscending, sortModelObjects, modelObjects=None): - VetoableEvent.__init__(self, olv_EVT_SORT) - self.objectListView = objectListView - self.sortColumnIndex = sortColumnIndex - self.sortAscending = sortAscending - self.sortModelObjects = sortModelObjects - self.modelObjects = modelObjects - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the ObjectListView. - The OLV will handle other tasks like updating sort indicators - """ - self.wasHandled = wasHandled - -#---------------------------------------------------------------------------- - -class GroupCreationEvent(wx.PyCommandEvent): - """ - The user is about to create one or more groups. - - The handler can mess with the list of groups before they are created: change their - names, give them icons, remove them from the list to stop them being created - (that last behaviour could be very confusing for the users). - """ - def __init__(self, objectListView, groups): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_CREATING, -1) - self.objectListView = objectListView - self.groups = groups - -#---------------------------------------------------------------------------- - -class ExpandCollapseEvent(VetoableEvent): - """ - The user wants to expand or collapse one or more groups, or has just done so. - - If the handler calls Veto() for a Expanding or Collapsing event, - the expand/collapse action will be cancelled. - - Calling Veto() has no effect on a Expanded or Collapsed event - """ - def __init__(self, eventType, objectListView, groups, isExpand): - VetoableEvent.__init__(self, eventType) - self.objectListView = objectListView - self.groups = groups - self.isExpand = isExpand - -def ExpandingCollapsingEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDING, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSING, objectListView, groups, False) - -def ExpandedCollapsedEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDED, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSED, objectListView, groups, False) - -#---------------------------------------------------------------------------- - -class SortGroupsEvent(wx.PyCommandEvent): - """ - The given list of groups needs to be sorted. - - Both the groups themselves and the model objects within the group should be sorted. - - The handler should rearrange the list of groups in the order desired. - """ - def __init__(self, objectListView, groups, sortColumn, sortAscending): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_SORT, -1) - self.objectListView = objectListView - self.groups = groups - self.sortColumn = sortColumn - self.sortAscending = sortAscending - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the groups. - """ - self.wasHandled = wasHandled +# -*- coding: utf-8 -*- +#---------------------------------------------------------------------------- +# Name: OLVEvent.py +# Author: Phillip Piper +# Created: 3 April 2008 +# SVN-ID: $Id$ +# Copyright: (c) 2008 by Phillip Piper, 2008 +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/08/18 JPP Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events +# 2008/07/16 JPP Added group-related events +# 2008/06/19 JPP Added EVT_SORT +# 2008/05/26 JPP Fixed pyLint annoyances +# 2008/04/04 JPP Initial version complete +#---------------------------------------------------------------------------- +# To do: + +""" +The OLVEvent module holds all the events used by the ObjectListView module. +""" + +__author__ = "Phillip Piper" +__date__ = "3 August 2008" +__version__ = "1.1" + +import wx + +#====================================================================== +# Event ids and types + +def _EventMaker(): + evt = wx.NewEventType() + return (evt, wx.PyEventBinder(evt)) + +(olv_EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTING) = _EventMaker() +(olv_EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_STARTED) = _EventMaker() +(olv_EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHING) = _EventMaker() +(olv_EVT_CELL_EDIT_FINISHED, EVT_CELL_EDIT_FINISHED) = _EventMaker() +(olv_EVT_SORT, EVT_SORT) = _EventMaker() +(olv_EVT_GROUP_CREATING, EVT_GROUP_CREATING) = _EventMaker() +(olv_EVT_GROUP_SORT, EVT_GROUP_SORT) = _EventMaker() +(olv_EVT_EXPANDING, EVT_EXPANDING) = _EventMaker() +(olv_EVT_EXPANDED, EVT_EXPANDED) = _EventMaker() +(olv_EVT_COLLAPSING, EVT_COLLAPSING) = _EventMaker() +(olv_EVT_COLLAPSED, EVT_COLLAPSED) = _EventMaker() + +#====================================================================== +# Event parameter blocks + +class VetoableEvent(wx.PyCommandEvent): + """ + Base class for all cancellable actions + """ + + def __init__(self, evtType): + wx.PyCommandEvent.__init__(self, evtType, -1) + self.veto = False + + def Veto(self, isVetoed=True): + """ + Veto (or un-veto) this event + """ + self.veto = isVetoed + + def IsVetoed(self): + """ + Has this event been vetod? + """ + return self.veto + +#---------------------------------------------------------------------------- + +class CellEditEvent(VetoableEvent): + """ + Base class for all cell editing events + """ + + def SetParameters(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor): + self.objectListView = objectListView + self.rowIndex = rowIndex + self.subItemIndex = subItemIndex + self.rowModel = rowModel + self.cellValue = cellValue + self.editor = editor + +#---------------------------------------------------------------------------- + +class CellEditStartedEvent(CellEditEvent): + """ + A cell has started to be edited. + + All attributes are public and should be considered read-only. + """ + + def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): + CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTED) + self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) + self.cellBounds = cellBounds + +#---------------------------------------------------------------------------- + +class CellEditStartingEvent(CellEditEvent): + """ + A cell is about to be edited. + + All attributes are public and should be considered read-only. Methods are provided for + information that can be changed. + """ + + def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): + CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTING) + self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) + self.cellBounds = cellBounds + self.newEditor = None + self.shouldConfigureEditor = True + + def SetCellBounds(self, rect): + """ + Change where the editor will be placed. + rect is a list: [left, top, width, height] + """ + self.cellBounds = rect + + def SetNewEditor(self, control): + """ + Use the given control instead of the editor. + """ + self.newEditor = control + + def DontConfigureEditor(self): + """ + The editor will not be automatically configured. + + If this is called, the event handler must handle all configuration. In + particular, it must configure its own event handlers to that + ObjectListView.CancelCellEdit() is called when the user presses Escape, + and ObjectListView.CommitCellEdit() is called when the user presses + Enter/Return or when the editor loses focus. """ + self.shouldConfigureEditor = False + +#---------------------------------------------------------------------------- + +class CellEditFinishedEvent(CellEditEvent): + """ + The user has finished editing a cell. + """ + def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, userCancelled): + CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHED) + self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, None, None) + self.userCancelled = userCancelled + +#---------------------------------------------------------------------------- + +class CellEditFinishingEvent(CellEditEvent): + """ + The user is finishing editing a cell. + + If this event is vetoed, the edit will be cancelled silently. This is useful if the + event handler completely handles the model updating. + """ + def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor, userCancelled): + CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHING) + self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) + self.userCancelled = userCancelled + + def SetCellValue(self, value): + """ + If the event handler sets the cell value here, this value will be used to update the model + object, rather than the value that was actually in the cell editor + """ + self.cellValue = value + +#---------------------------------------------------------------------------- + +class SortEvent(VetoableEvent): + """ + The user wants to sort the ObjectListView. + + When sortModelObjects is True, the event handler should sort the model objects used by + the given ObjectListView. If the "modelObjects" instance variable is not None, that + collection of objects should be sorted, otherwise the "modelObjects" collection of the + ObjectListView should be sorted. For a VirtualObjectListView, "modelObjects" will + always be None and the programmer must sort the object in whatever backing store is + being used. + + When sortModelObjects is False, the event handler must sort the actual ListItems in + the OLV. It does this by calling SortListItemsBy(), passing a callable that accepts + two model objects as parameters. sortModelObjects must be True for a + VirtualObjectListView (or a FastObjectListView) since virtual lists cannot sort items. + + If the handler calls Veto(), no further default processing will be done. + If the handler calls Handled(), default processing concerned with UI will be done. This + includes updating sort indicators. + If the handler calls neither of these, all default processing will be done. + """ + def __init__(self, objectListView, sortColumnIndex, sortAscending, sortModelObjects, modelObjects=None): + VetoableEvent.__init__(self, olv_EVT_SORT) + self.objectListView = objectListView + self.sortColumnIndex = sortColumnIndex + self.sortAscending = sortAscending + self.sortModelObjects = sortModelObjects + self.modelObjects = modelObjects + self.wasHandled = False + + def Handled(self, wasHandled=True): + """ + Indicate that the event handler has sorted the ObjectListView. + The OLV will handle other tasks like updating sort indicators + """ + self.wasHandled = wasHandled + +#---------------------------------------------------------------------------- + +class GroupCreationEvent(wx.PyCommandEvent): + """ + The user is about to create one or more groups. + + The handler can mess with the list of groups before they are created: change their + names, give them icons, remove them from the list to stop them being created + (that last behaviour could be very confusing for the users). + """ + def __init__(self, objectListView, groups): + wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_CREATING, -1) + self.objectListView = objectListView + self.groups = groups + +#---------------------------------------------------------------------------- + +class ExpandCollapseEvent(VetoableEvent): + """ + The user wants to expand or collapse one or more groups, or has just done so. + + If the handler calls Veto() for a Expanding or Collapsing event, + the expand/collapse action will be cancelled. + + Calling Veto() has no effect on a Expanded or Collapsed event + """ + def __init__(self, eventType, objectListView, groups, isExpand): + VetoableEvent.__init__(self, eventType) + self.objectListView = objectListView + self.groups = groups + self.isExpand = isExpand + +def ExpandingCollapsingEvent(objectListView, groups, isExpand): + if isExpand: + return ExpandCollapseEvent(olv_EVT_EXPANDING, objectListView, groups, True) + else: + return ExpandCollapseEvent(olv_EVT_COLLAPSING, objectListView, groups, False) + +def ExpandedCollapsedEvent(objectListView, groups, isExpand): + if isExpand: + return ExpandCollapseEvent(olv_EVT_EXPANDED, objectListView, groups, True) + else: + return ExpandCollapseEvent(olv_EVT_COLLAPSED, objectListView, groups, False) + +#---------------------------------------------------------------------------- + +class SortGroupsEvent(wx.PyCommandEvent): + """ + The given list of groups needs to be sorted. + + Both the groups themselves and the model objects within the group should be sorted. + + The handler should rearrange the list of groups in the order desired. + """ + def __init__(self, objectListView, groups, sortColumn, sortAscending): + wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_SORT, -1) + self.objectListView = objectListView + self.groups = groups + self.sortColumn = sortColumn + self.sortAscending = sortAscending + self.wasHandled = False + + def Handled(self, wasHandled=True): + """ + Indicate that the event handler has sorted the groups. + """ + self.wasHandled = wasHandled diff --git a/odmtools/lib/oldOlv/ObjectListView.py b/odmtools/lib/oldOlv/ObjectListView.py old mode 100755 new mode 100644 diff --git a/odmtools/lib/oldOlv/WordWrapRenderer.py b/odmtools/lib/oldOlv/WordWrapRenderer.py index 4920b3a..c4dd1c9 100644 --- a/odmtools/lib/oldOlv/WordWrapRenderer.py +++ b/odmtools/lib/oldOlv/WordWrapRenderer.py @@ -1,232 +1,232 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: WordWrapRenderer.py -# Author: Phillip Piper -# Created: 25 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/25 JPP Initial version -#---------------------------------------------------------------------------- -# To do: - -""" -A WordWrapRenderer encapsulates the ability to draw and measure word wrapped -strings directly to a device context. - -It is meant to be good enough for general use. It is not suitable for typographic layout --- it does not handle kerning or ligatures. - -The DC passed to these methods cannot be a GraphicContext DC. These methods use -GetPartialTextExtents() which does not work with GCDC's (as of wx 2.8). - -""" - -import wx -import bisect -from wx.lib.wordwrap import wordwrap - -class WordWrapRenderer: - """ - This renderer encapsulates the logic need to draw and measure a word-wrapped - string within a given rectangle. - """ - - #---------------------------------------------------------------------------- - # Calculating - - @staticmethod - def CalculateHeight(dc, text, width): - """ - Calculate the height of the given text when fitted within the given width. - - Remember to set the font on the dc before calling this method. - """ - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, width, dc, True) - (width, height, descent, externalLeading) = dc.GetFullTextExtent("Wy") - return (lines.count("\n")+1) * (height + externalLeading) - - - #---------------------------------------------------------------------------- - # Rendering - - @staticmethod - def DrawString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, allowClipping=False): - """ - Draw the given text word-wrapped within the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - # DrawLabel only accepts a wx.Rect - try: - bounds = wx.Rect(*bounds) - except: - pass - - if allowClipping: - clipper = wx.DCClipper(dc, bounds) - - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, bounds[2], dc, True) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def DrawTruncatedString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, ellipse=wx.RIGHT, ellipseChars="..."): - """ - Draw the given text truncated to the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - try: - bounds = wx.Rect(*bounds) - except: - pass - lines = WordWrapRenderer._Truncate(dc, text, bounds[2], ellipse, ellipseChars) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def _Truncate(dc, text, maxWidth, ellipse, ellipseChars): - """ - Return a string that will fit within the given width. - """ - line = text.split("\n")[0] # only consider the first line - if not line: - return "" - - pte = dc.GetPartialTextExtents(line) - - # Does the whole thing fit within our given width? - stringWidth = pte[-1] - if stringWidth <= maxWidth: - return line - - # We (probably) have to ellipse the text so allow for ellipse - maxWidthMinusEllipse = maxWidth - dc.GetTextExtent(ellipseChars)[0] - - if ellipse == wx.LEFT: - i = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse) - return ellipseChars + line[i+1:] - - if ellipse == wx.CENTER: - i = bisect.bisect(pte, maxWidthMinusEllipse / 2) - j = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse / 2) - return line[:i] + ellipseChars + line[j+1:] - - if ellipse == wx.RIGHT: - i = bisect.bisect(pte, maxWidthMinusEllipse) - return line[:i] + ellipseChars - - # No ellipsing, just truncating is the default - i = bisect.bisect(pte, maxWidth) - return line[:i] - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - - class TestPanel(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent, -1, style=wx.FULL_REPAINT_ON_RESIZE) - self.Bind(wx.EVT_PAINT, self.OnPaint) - - self.text = """This is Thisisareallylongwordtoseewhathappens the text to be drawn. It needs to be long to see if wrapping works. to long words. -This is on new line by itself. - -This should have a blank line in front of it but still wrap when we reach the edge. - -The bottom of the red rectangle should be immediately below this.""" - self.font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Gill Sans") - - def OnPaint(self, evt): - dc = wx.PaintDC(self) - inset = (20, 20, 20, 20) - rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - - # Calculate exactly how high the wrapped is going to be and put a frame around it. - dc.SetFont(self.font) - dc.SetPen(wx.RED_PEN) - rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) - dc.DrawRectangle(*rect) - WordWrapRenderer.DrawString(dc, self.text, rect, wx.ALIGN_LEFT) - #WordWrapRenderer.DrawTruncatedString(dc, self.text, rect, wx.ALIGN_CENTER_HORIZONTAL,s ellipse=wx.CENTER) - - #bmp = wx.EmptyBitmap(rect[0]+rect[2], rect[1]+rect[3]) - #mdc = wx.MemoryDC(bmp) - #mdc.SetBackground(wx.Brush("white")) - #mdc.Clear() - #mdc.SetFont(self.font) - #mdc.SetPen(wx.RED_PEN) - #rect[3] = WordWrapRenderer.CalculateHeight(mdc, self.text, rect[2]) - #mdc.DrawRectangle(*rect) - #WordWrapRenderer.DrawString(mdc, self.text, rect, wx.ALIGN_LEFT) - #del mdc - #dc = wx.ScreenDC() - #dc.DrawBitmap(bmp, 20, 20) - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.testPanel = TestPanel(self.panel) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.testPanel, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() +# -*- coding: utf-8 -*- +#!/usr/bin/env python +#---------------------------------------------------------------------------- +# Name: WordWrapRenderer.py +# Author: Phillip Piper +# Created: 25 July 2008 +# SVN-ID: $Id$ +# Copyright: (c) 2008 by Phillip Piper, 2008 +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/07/25 JPP Initial version +#---------------------------------------------------------------------------- +# To do: + +""" +A WordWrapRenderer encapsulates the ability to draw and measure word wrapped +strings directly to a device context. + +It is meant to be good enough for general use. It is not suitable for typographic layout +-- it does not handle kerning or ligatures. + +The DC passed to these methods cannot be a GraphicContext DC. These methods use +GetPartialTextExtents() which does not work with GCDC's (as of wx 2.8). + +""" + +import wx +import bisect +from wx.lib.wordwrap import wordwrap + +class WordWrapRenderer: + """ + This renderer encapsulates the logic need to draw and measure a word-wrapped + string within a given rectangle. + """ + + #---------------------------------------------------------------------------- + # Calculating + + @staticmethod + def CalculateHeight(dc, text, width): + """ + Calculate the height of the given text when fitted within the given width. + + Remember to set the font on the dc before calling this method. + """ + # There is a bug in the wordwrap routine where a string that needs truncated and + # that ends with a single space causes the method to throw an error (wx 2.8). + # Our simple, but not always accurate, is to remove trailing spaces. + # This won't catch single trailing space imbedded in a multiline string. + text = text.rstrip(' ') + + lines = wordwrap(text, width, dc, True) + (width, height, descent, externalLeading) = dc.GetFullTextExtent("Wy") + return (lines.count("\n")+1) * (height + externalLeading) + + + #---------------------------------------------------------------------------- + # Rendering + + @staticmethod + def DrawString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, allowClipping=False): + """ + Draw the given text word-wrapped within the given bounds. + + bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). + + If allowClipping is True, this method changes the clipping region so that no + text is drawn outside of the given bounds. + """ + if not text: + return + + if align == wx.ALIGN_CENTER: + align = wx.ALIGN_CENTER_HORIZONTAL + + if valign == wx.ALIGN_CENTER: + valign = wx.ALIGN_CENTER_VERTICAL + + # DrawLabel only accepts a wx.Rect + try: + bounds = wx.Rect(*bounds) + except: + pass + + if allowClipping: + clipper = wx.DCClipper(dc, bounds) + + # There is a bug in the wordwrap routine where a string that needs truncated and + # that ends with a single space causes the method to throw an error (wx 2.8). + # Our simple, but not always accurate, is to remove trailing spaces. + # This won't catch single trailing space imbedded in a multiline string. + text = text.rstrip(' ') + + lines = wordwrap(text, bounds[2], dc, True) + dc.DrawLabel(lines, bounds, align|valign) + + + @staticmethod + def DrawTruncatedString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, ellipse=wx.RIGHT, ellipseChars="..."): + """ + Draw the given text truncated to the given bounds. + + bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). + + If allowClipping is True, this method changes the clipping region so that no + text is drawn outside of the given bounds. + """ + if not text: + return + + if align == wx.ALIGN_CENTER: + align = wx.ALIGN_CENTER_HORIZONTAL + + if valign == wx.ALIGN_CENTER: + valign = wx.ALIGN_CENTER_VERTICAL + + try: + bounds = wx.Rect(*bounds) + except: + pass + lines = WordWrapRenderer._Truncate(dc, text, bounds[2], ellipse, ellipseChars) + dc.DrawLabel(lines, bounds, align|valign) + + + @staticmethod + def _Truncate(dc, text, maxWidth, ellipse, ellipseChars): + """ + Return a string that will fit within the given width. + """ + line = text.split("\n")[0] # only consider the first line + if not line: + return "" + + pte = dc.GetPartialTextExtents(line) + + # Does the whole thing fit within our given width? + stringWidth = pte[-1] + if stringWidth <= maxWidth: + return line + + # We (probably) have to ellipse the text so allow for ellipse + maxWidthMinusEllipse = maxWidth - dc.GetTextExtent(ellipseChars)[0] + + if ellipse == wx.LEFT: + i = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse) + return ellipseChars + line[i+1:] + + if ellipse == wx.CENTER: + i = bisect.bisect(pte, maxWidthMinusEllipse / 2) + j = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse / 2) + return line[:i] + ellipseChars + line[j+1:] + + if ellipse == wx.RIGHT: + i = bisect.bisect(pte, maxWidthMinusEllipse) + return line[:i] + ellipseChars + + # No ellipsing, just truncating is the default + i = bisect.bisect(pte, maxWidth) + return line[:i] + +#====================================================================== +# TESTING ONLY +#====================================================================== + +if __name__ == '__main__': + + class TestPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1, style=wx.FULL_REPAINT_ON_RESIZE) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + self.text = """This is Thisisareallylongwordtoseewhathappens the text to be drawn. It needs to be long to see if wrapping works. to long words. +This is on new line by itself. + +This should have a blank line in front of it but still wrap when we reach the edge. + +The bottom of the red rectangle should be immediately below this.""" + self.font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Gill Sans") + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + inset = (20, 20, 20, 20) + rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] + + # Calculate exactly how high the wrapped is going to be and put a frame around it. + dc.SetFont(self.font) + dc.SetPen(wx.RED_PEN) + rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) + dc.DrawRectangle(*rect) + WordWrapRenderer.DrawString(dc, self.text, rect, wx.ALIGN_LEFT) + #WordWrapRenderer.DrawTruncatedString(dc, self.text, rect, wx.ALIGN_CENTER_HORIZONTAL,s ellipse=wx.CENTER) + + #bmp = wx.EmptyBitmap(rect[0]+rect[2], rect[1]+rect[3]) + #mdc = wx.MemoryDC(bmp) + #mdc.SetBackground(wx.Brush("white")) + #mdc.Clear() + #mdc.SetFont(self.font) + #mdc.SetPen(wx.RED_PEN) + #rect[3] = WordWrapRenderer.CalculateHeight(mdc, self.text, rect[2]) + #mdc.DrawRectangle(*rect) + #WordWrapRenderer.DrawString(mdc, self.text, rect, wx.ALIGN_LEFT) + #del mdc + #dc = wx.ScreenDC() + #dc.DrawBitmap(bmp, 20, 20) + + class MyFrame(wx.Frame): + def __init__(self, *args, **kwds): + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + + self.panel = wx.Panel(self, -1) + self.testPanel = TestPanel(self.panel) + + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.testPanel, 1, wx.ALL|wx.EXPAND, 4) + self.panel.SetSizer(sizer_2) + self.panel.Layout() + + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_1.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(sizer_1) + self.Layout() + + + app = wx.PySimpleApp(0) + wx.InitAllImageHandlers() + frame_1 = MyFrame(None, -1, "") + app.SetTopWindow(frame_1) + frame_1.Show() + app.MainLoop() diff --git a/odmtools/lib/oldOlv/t.py b/odmtools/lib/oldOlv/t.py index a54cdd1..f3a546c 100644 --- a/odmtools/lib/oldOlv/t.py +++ b/odmtools/lib/oldOlv/t.py @@ -1,228 +1,228 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Initial version -#---------------------------------------------------------------------------- -# To do: -# - scaling -# - gradients -# - images -# - attributes from ListCtrl -# - persistence of ReportFormat -# - use wx.wordwrap and DrawLabel -# - investigate DrawImageLabel - -""" -An ListCtrlPrinter takes an ObjectListView and turns it into a pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -""" - -import wx - -#====================================================================== - -class TestPrinter(wx.Printout): - - def __init__(self, margins=None): - """ - """ - wx.Printout.__init__(self, "The title") - - self.printData = wx.PrintData() - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetOrientation(wx.PORTRAIT) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - self.printData.SetNoCopies(1) - self.margins = margins or (wx.Point(0, 0), wx.Point(0, 0)) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - return page <= 3 - - def GetPageInfo(self): - return (1, 3, 1, 1) - - #---------------------------------------------------------------------------- - # Commands - - - def PageSetup(self): - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(None, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), - data.GetMarginBottomRight()) - dlg.Destroy() - - def PrintPreview(self, parent=None, title="ObjectListView Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - data = wx.PrintDialogData(self.printData) - t = TestPrinter(self.margins) - t2 = TestPrinter(self.margins) - self.preview = wx.PrintPreview(t, t2, data) - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent=None): - """ - Send the report to the configured printer - """ - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - - printout.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - print "OnPreparePrinting" - print "self.GetDC() = %s" % self.GetDC() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - print "OnBeginDocument(%d, %d)" % (start, end) - if not super(TestPrinter, self).OnBeginDocument(start, end): - return False - - return True - - def OnEndDocument(self): - print "OnEndDocument" - super(TestPrinter, self).OnEndDocument() - - def OnBeginPrinting(self): - print "OnBeginPrinting" - super(TestPrinter, self).OnBeginPrinting() - - def OnEndPrinting(self): - print "OnEndPrinting" - super(TestPrinter, self).OnEndPrinting() - - def OnPrintPage(self, page): - print "OnPrintPage(%d)" % page - dc = self.GetDC() - self.CalculateScale(dc) - self.CalculateLayout(dc) - dc.SetPen(wx.BLACK_PEN) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - bounds = (self.x1, self.y1, self.x2-self.x1, self.y2-self.y1) - print bounds - print self.pageHeight - dc.DrawRectangle(*bounds) - font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL) - dc.SetFont(font) - dc.DrawText("this is a string", bounds[0], bounds[1]) - - def CalculateScale(self, dc): - # Scaling the DC to screen size - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logScale = float(ppiPrinterX)/float(ppiScreenX) - pw, ph = self.GetPageSizePixels() # Adjusting scale - dw, dh = dc.GetSize() - scale = logScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4) - - def CalculateLayout(self, dc): - topLeft, bottomRight = self.margins - dw, dh = dc.GetSize() - self.x1 = topLeft.x * self.logUnitsMM - self.y1 = topLeft.y * self.logUnitsMM - self.x2 = dc.DeviceToLogicalYRel(dw) - bottomRight.x * self.logUnitsMM - self.y2 = dc.DeviceToLogicalYRel(dh) - bottomRight.y * self.logUnitsMM - self.pageHeight = self.y2 - self.y1 - 2*self.logUnitsMM - - - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - import wx - - # Where can we find the Example module? - import sys - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.olv = wx.ListCtrl(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.olv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - wx.CallLater(50, self.run) - - def run(self): - printer = TestPrinter() - printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() +# -*- coding: utf-8 -*- +#!/usr/bin/env python +#---------------------------------------------------------------------------- +# Name: ListCtrlPrinter.py +# Author: Phillip Piper +# Created: 17 July 2008 +# SVN-ID: $Id$ +# Copyright: (c) 2008 by Phillip Piper, 2008 +# License: wxWindows license +#---------------------------------------------------------------------------- +# Change log: +# 2008/07/17 JPP Initial version +#---------------------------------------------------------------------------- +# To do: +# - scaling +# - gradients +# - images +# - attributes from ListCtrl +# - persistence of ReportFormat +# - use wx.wordwrap and DrawLabel +# - investigate DrawImageLabel + +""" +An ListCtrlPrinter takes an ObjectListView and turns it into a pretty report. + +As always, the goal is for this to be as easy to use as possible. A typical +usage should be as simple as:: + + printer = ListCtrlPrinter(self.myOlv, "My Report Title") + printer.PrintPreview() + +""" + +import wx + +#====================================================================== + +class TestPrinter(wx.Printout): + + def __init__(self, margins=None): + """ + """ + wx.Printout.__init__(self, "The title") + + self.printData = wx.PrintData() + self.printData.SetPaperId(wx.PAPER_A4) + self.printData.SetOrientation(wx.PORTRAIT) + self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) + self.printData.SetNoCopies(1) + self.margins = margins or (wx.Point(0, 0), wx.Point(0, 0)) + + + #---------------------------------------------------------------------------- + # Accessing + + def HasPage(self, page): + return page <= 3 + + def GetPageInfo(self): + return (1, 3, 1, 1) + + #---------------------------------------------------------------------------- + # Commands + + + def PageSetup(self): + data = wx.PageSetupDialogData() + data.SetPrintData(self.printData) + data.SetDefaultMinMargins(True) + data.SetMarginTopLeft(self.margins[0]) + data.SetMarginBottomRight(self.margins[1]) + dlg = wx.PageSetupDialog(None, data) + if dlg.ShowModal() == wx.ID_OK: + data = dlg.GetPageSetupData() + self.printData = wx.PrintData(data.GetPrintData()) + self.printData.SetPaperId(data.GetPaperId()) + self.margins = (data.GetMarginTopLeft(), + data.GetMarginBottomRight()) + dlg.Destroy() + + def PrintPreview(self, parent=None, title="ObjectListView Print Preview", bounds=(20, 50, 800, 800)): + """ + Show a Print Preview of this report + """ + data = wx.PrintDialogData(self.printData) + t = TestPrinter(self.margins) + t2 = TestPrinter(self.margins) + self.preview = wx.PrintPreview(t, t2, data) + + if not self.preview.Ok(): + return False + + pfrm = wx.PreviewFrame(self.preview, parent, title) + + pfrm.Initialize() + pfrm.SetPosition(bounds[0:2]) + pfrm.SetSize(bounds[2:4]) + pfrm.Show(True) + + return True + + + def DoPrint(self, parent=None): + """ + Send the report to the configured printer + """ + pdd = wx.PrintDialogData(self.printData) + printer = wx.Printer(pdd) + + if printer.Print(parent, self, True): + self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) + else: + wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) + + printout.Destroy() + + + #---------------------------------------------------------------------------- + # Event handlers + + def OnPreparePrinting(self): + """ + Prepare for printing. This event is sent before any of the others + """ + print "OnPreparePrinting" + print "self.GetDC() = %s" % self.GetDC() + + def OnBeginDocument(self, start, end): + """ + Begin printing one copy of the document. Return False to cancel the job + """ + print "OnBeginDocument(%d, %d)" % (start, end) + if not super(TestPrinter, self).OnBeginDocument(start, end): + return False + + return True + + def OnEndDocument(self): + print "OnEndDocument" + super(TestPrinter, self).OnEndDocument() + + def OnBeginPrinting(self): + print "OnBeginPrinting" + super(TestPrinter, self).OnBeginPrinting() + + def OnEndPrinting(self): + print "OnEndPrinting" + super(TestPrinter, self).OnEndPrinting() + + def OnPrintPage(self, page): + print "OnPrintPage(%d)" % page + dc = self.GetDC() + self.CalculateScale(dc) + self.CalculateLayout(dc) + dc.SetPen(wx.BLACK_PEN) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + bounds = (self.x1, self.y1, self.x2-self.x1, self.y2-self.y1) + print bounds + print self.pageHeight + dc.DrawRectangle(*bounds) + font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL) + dc.SetFont(font) + dc.DrawText("this is a string", bounds[0], bounds[1]) + + def CalculateScale(self, dc): + # Scaling the DC to screen size + ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() + ppiScreenX, ppiScreenY = self.GetPPIScreen() + logScale = float(ppiPrinterX)/float(ppiScreenX) + pw, ph = self.GetPageSizePixels() # Adjusting scale + dw, dh = dc.GetSize() + scale = logScale * float(dw)/float(pw) + dc.SetUserScale(scale, scale) + self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4) + + def CalculateLayout(self, dc): + topLeft, bottomRight = self.margins + dw, dh = dc.GetSize() + self.x1 = topLeft.x * self.logUnitsMM + self.y1 = topLeft.y * self.logUnitsMM + self.x2 = dc.DeviceToLogicalYRel(dw) - bottomRight.x * self.logUnitsMM + self.y2 = dc.DeviceToLogicalYRel(dh) - bottomRight.y * self.logUnitsMM + self.pageHeight = self.y2 - self.y1 - 2*self.logUnitsMM + + + +#====================================================================== +# TESTING ONLY +#====================================================================== + +if __name__ == '__main__': + import wx + + # Where can we find the Example module? + import sys + + class MyFrame(wx.Frame): + def __init__(self, *args, **kwds): + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + + self.panel = wx.Panel(self, -1) + self.olv = wx.ListCtrl(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) + + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.olv, 1, wx.ALL|wx.EXPAND, 4) + self.panel.SetSizer(sizer_2) + self.panel.Layout() + + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_1.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(sizer_1) + self.Layout() + + wx.CallLater(50, self.run) + + def run(self): + printer = TestPrinter() + printer.PageSetup() + printer.PrintPreview(self) + + + app = wx.PySimpleApp(0) + wx.InitAllImageHandlers() + frame_1 = MyFrame(None, -1, "") + app.SetTopWindow(frame_1) + frame_1.Show() + app.MainLoop() diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index dd2a352..dfb89a2 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -14,7 +14,7 @@ from sample_medium_cv import SampleMediumCV from sample_type_cv import SampleTypeCV from series import Series -# from session_factory import SessionFactory +from session_factory import SessionFactory from site import Site from site_type_cv import SiteTypeCV from source import Source diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py index 8afda48..0d2d488 100644 --- a/odmtools/odmdata/session_factory.py +++ b/odmtools/odmdata/session_factory.py @@ -1,57 +1,57 @@ -# from sqlalchemy import create_engine -# from sqlalchemy.orm import sessionmaker -# -# -# class SessionFactory(): -# def __init__(self, connection_string, echo): -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_size=20) -# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'connect_timeout': 1}) -# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'timeout': 1}) -# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'connect_timeout': 1}) -# -# ''' -# # Removing pool_timeout and max_overflow allowed the tests to pass -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, pool_size=20, max_overflow=0) -# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) -# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) -# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) -# ''' -# -# ''' -# # Old code -# class SessionFactory(): -# def __init__(self, connection_string, echo): -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, -# #pool_size=20, -# pool_recycle=3600) -# -# # Create session maker -# self.Session = sessionmaker(bind=self.engine) -# -# def get_session(self): -# return self.Session() -# -# def __repr__(self): -# return "" % (self.engine) -# ''' -# -# # Create session maker -# self.Session = sessionmaker(bind=self.engine) -# self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) -# self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) -# self.my_test_Session = sessionmaker(bind=self.my_test_engine) -# -# def get_session(self): -# return self.Session() -# -# def __repr__(self): -# return "" % (self.engine) -# +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + + +class SessionFactory(): + def __init__(self, connection_string, echo): + self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + pool_size=20) + self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + connect_args={'connect_timeout': 1}) + self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + connect_args={'timeout': 1}) + self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + connect_args={'connect_timeout': 1}) + + ''' + # Removing pool_timeout and max_overflow allowed the tests to pass + self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + pool_timeout=5, pool_size=20, max_overflow=0) + self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) + self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) + self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, + pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) + ''' + + ''' + # Old code + class SessionFactory(): + def __init__(self, connection_string, echo): + self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, + #pool_size=20, + pool_recycle=3600) + + # Create session maker + self.Session = sessionmaker(bind=self.engine) + + def get_session(self): + return self.Session() + + def __repr__(self): + return "" % (self.engine) + ''' + + # Create session maker + self.Session = sessionmaker(bind=self.engine) + self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) + self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) + self.my_test_Session = sessionmaker(bind=self.my_test_engine) + + def get_session(self): + return self.Session() + + def __repr__(self): + return "" % (self.engine) + diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index b1a01b2..24b5e81 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -1,5 +1,5 @@ # CV imports -# from odmtools.odmdata import SessionFactory +from odmtools.odmdata import SessionFactory from odmtools.odmdata import VerticalDatumCV from odmtools.odmdata import SiteTypeCV from odmtools.odmdata import VariableNameCV @@ -16,87 +16,69 @@ from odmtools.odmdata import Qualifier from odmtools.odmdata import Unit from sqlalchemy import not_ -from odm2api.ODMconnection import SessionFactory -from odm2api.ODM2.services.readService import ReadODM2 -class CVService(): # change to readSerivice() +class CVService(): # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string=connection_string, echo=debug) - self._edit_session = self._session_factory.getSession() + self._session_factory = SessionFactory(connection_string, debug) + self._edit_session = self._session_factory.get_session() self._debug = debug - self.read_service = ReadODM2(session_factory=self._session_factory, debug=self._debug) # Controlled Vocabulary get methods - # From ODM1 -> ODM2 Qualifier was changed to Annotations - def get_annotations(self, type): - return self.read_service.getAnnotations(type=type) + #return a list of all terms in the cv + def get_vertical_datum_cvs(self): + result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() + return result - def get_censor_code_cvs(self): - result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() + def get_samples(self): + result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() return result - def get_data_type_cvs(self): - result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() + def get_site_type_cvs(self): + result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() return result - def get_general_category_cvs(self): - result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() + def get_variable_name_cvs(self): + result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() return result def get_offset_type_cvs(self): - return self.read_service.getCVs(type="Spatial Offset Type") - - # From ODM1 -> ODM2 Quality Controlled Level was changed to Processing Level - def get_all_processing_levels(self): - self.read_service.getProcessingLevels() - - def get_all_method(self): # Rename to get_method_all - return self.read_service.getMethods(ids=None, codes=None, type=None) - - def get_method_by_id(self, method_id): - return self.read_service.getMethods(ids=method_id) - - def get_method_by_description(self, code): - return self.read_service.getMethods(codes=code) + result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() + return result - def get_processing_level_by_id(self, id): - self.read_service.getProcessingLevels(ids=id) + def get_speciation_cvs(self): + result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() + return result - def get_processing_level_by_code(self, code): - self.read_service.getProcessingLevels(codes=code) + def get_sample_medium_cvs(self): + result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() + return result - def get_samples(self): - result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() + def get_value_type_cvs(self): + result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() return result - def get_sample_medium_cvs(self): - return self.read_service.getCVs(type="Medium") + def get_data_type_cvs(self): + result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() + return result - def get_site_type_cvs(self): - return self.read_service.getCVs(type="Site Type") + def get_general_category_cvs(self): + result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() + return result - def get_speciation_cvs(self): - return self.read_service.getCVs(type="Speciation") + def get_censor_code_cvs(self): + result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() + return result def get_sample_type_cvs(self): result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() return result - # From ODM1 -> ODM2 Site was changed to Sampling Feature - def get_all_sites(self): - return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) - - def get_site_by_id(self, site_id): - return self.read_service.getSamplingFeatures(ids=site_id) - - def get_timeseries_result_values(self, type): - return self.read_service.getAnnotations(type=type) - def get_units(self): - self.read_service.getUnits(ids=None, name=None, type=None) + result = self._edit_session.query(Unit).all() + return result def get_units_not_uni(self): result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() @@ -108,31 +90,9 @@ def get_units_names(self): # return a single cv def get_unit_by_name(self, unit_name): - return self.read_service.getUnits(name=unit_name) + result = self._edit_session.query(Unit).filter_by(name=unit_name).first() + return result def get_unit_by_id(self, unit_id): - return self.read_service.getUnits(ids=unit_id) - - def get_value_type_cvs(self): - result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() + result = self._edit_session.query(Unit).filter_by(id=unit_id).first() return result - - def get_variable_name_cvs(self): - return self.read_service.getCVs(type="Variable Name") - - def get_vertical_datum_cvs(self): - return self.read_service.getCVs("Elevation Datum") - - def get_all_variables(self): - return self.read_service.getVariables() - - def get_variable_by_id(self, id): - return self.read_service.getVariables(ids=id) - - def get_variable_by_code(self, code): - return self.read_service.getVariables(codes=code) - - - - - diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 7f6b136..078e6fe 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,6 +1,10 @@ import logging + + from sqlalchemy import distinct, func -from odm2api.ODMconnection import SessionFactory # from odmtools.odmdata import SessionFactory + + +from odmtools.odmdata import SessionFactory from odmtools.odmdata import Site from odmtools.odmdata import Variable from odmtools.odmdata import Unit @@ -14,23 +18,20 @@ from odmtools.odmdata import ODMVersion from odmtools.common.logger import LoggerTool import pandas as pd -from odm2api.ODM2.services.createService import CreateODM2 # tool = LoggerTool() # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger = logging.getLogger('main') +logger =logging.getLogger('main') -class SeriesService(): # Change to createService +class SeriesService(): # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string=connection_string, echo=debug) - self._edit_session = self._session_factory.getSession() + self._session_factory = SessionFactory(connection_string, debug) + self._edit_session = self._session_factory.get_session() self._debug = debug - self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) def reset_session(self): - # self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks - self._edit_session = self.create_service.getSession() + self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks def get_db_version(self): return self._edit_session.query(ODMVersion).first().version_number @@ -41,6 +42,15 @@ def get_db_version(self): # ##################### + # Site methods + def get_all_sites(self): + """ + + :return: List[Sites] + """ + return self._edit_session.query(Site).order_by(Site.code).all() + + def get_used_sites(self): """ Return a list of all sites that are being referenced in the Series Catalog Table @@ -49,7 +59,7 @@ def get_used_sites(self): try: site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] except: - return None + site_ids = None if not site_ids: return None @@ -60,6 +70,18 @@ def get_used_sites(self): return Sites + + def get_site_by_id(self, site_id): + """ + return a Site object that has an id=site_id + :param site_id: integer- the identification number of the site + :return: Sites + """ + try: + return self._edit_session.query(Site).filter_by(id=site_id).first() + except: + return None + # Variables methods def get_used_variables(self): """ @@ -83,6 +105,145 @@ def get_used_variables(self): return Variables + def get_all_variables(self): + """ + + :return: List[Variables] + """ + return self._edit_session.query(Variable).all() + + def get_variable_by_id(self, variable_id): + """ + + :param variable_id: int + :return: Variables + """ + try: + return self._edit_session.query(Variable).filter_by(id=variable_id).first() + except: + return None + + def get_variable_by_code(self, variable_code): + """ + + :param variable_code: str + :return: Variables + """ + try: + return self._edit_session.query(Variable).filter_by(code=variable_code).first() + except: + return None + + def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits + """ + Finds all of variables at a site + :param site_code: str + :return: List[Variables] + """ + try: + var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( + site_code=site_code).all()] + except: + var_ids = None + + variables = [] + for var_id in var_ids: + variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) + + return variables + + # Unit methods + def get_all_units(self): + """ + + :return: List[Units] + """ + return self._edit_session.query(Unit).all() + + def get_unit_by_name(self, unit_name): + """ + + :param unit_name: str + :return: Units + """ + try: + return self._edit_session.query(Unit).filter_by(name=unit_name).first() + except: + return None + + def get_unit_by_id(self, unit_id): + """ + + :param unit_id: int + :return: Units + """ + try: + return self._edit_session.query(Unit).filter_by(id=unit_id).first() + except: + return None + + + def get_all_qualifiers(self): + """ + + :return: List[Qualifiers] + """ + result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() + return result + + def get_qualifier_by_code(self, code): + """ + + :return: Qualifiers + """ + result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + return result + + def get_qualifiers_by_series_id(self, series_id): + """ + + :param series_id: + :return: + """ + subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( + Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() + return self._edit_session.query(Qualifier).join(subquery).distinct().all() + + #QCL methods + def get_all_qcls(self): + return self._edit_session.query(QualityControlLevel).all() + + def get_qcl_by_id(self, qcl_id): + try: + return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() + except: + return None + + def get_qcl_by_code(self, qcl_code): + try: + return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() + except: + return None + + # Method methods + def get_all_methods(self): + return self._edit_session.query(Method).all() + + def get_method_by_id(self, method_id): + try: + result = self._edit_session.query(Method).filter_by(id=method_id).first() + except: + result = None + return result + + def get_method_by_description(self, method_code): + try: + result = self._edit_session.query(Method).filter_by(description=method_code).first() + except: + result = None + logger.error("method not found") + return result + def get_offset_types_by_series_id(self, series_id): """ @@ -158,31 +319,6 @@ def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass - def get_all_qualifiers(self): - """ - - :return: List[Qualifiers] - """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result - - def get_qualifier_by_code(self, code): - """ - - :return: Qualifiers - """ - result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - return result - - def get_qualifiers_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() #Data Value Methods def get_values_by_series(self, series_id): @@ -398,6 +534,7 @@ def save_values(self, values): def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): """ + :param data_values: :param site_id: :param variable_id: @@ -418,8 +555,9 @@ def create_new_series(self, data_values, site_id, variable_id, method_id, source self._edit_session.commit() return series - def create_method(self, description, link): # DONE + def create_method(self, description, link): """ + :param description: :param link: :return: @@ -429,23 +567,27 @@ def create_method(self, description, link): # DONE if link is not None: meth.link = link - self.create_service.createMethod(method=meth) - + self._edit_session.add(meth) + self._edit_session.commit() return meth - def create_variable_by_var(self, var): # DONE + def create_variable_by_var(self, var): """ + :param var: Variable Object :return: """ - - self.create_service.createVariable(var) - return var + try: + self._edit_session.add(var) + self._edit_session.commit() + return var + except: + return None def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): # DONE + general_category, no_data_value): """ :param code: @@ -476,10 +618,11 @@ def create_variable( var.general_category = general_category var.no_data_value = no_data_value - self.create_variable_by_var(var) + self._edit_session.add(var) + self._edit_session.commit() return var - def create_qcl(self, code, definition, explanation): # DONE + def create_qcl(self, code, definition, explanation): """ :param code: @@ -492,7 +635,8 @@ def create_qcl(self, code, definition, explanation): # DONE qcl.definition = definition qcl.explanation = explanation - self.create_service.createProcessingLevel(qcl) + self._edit_session.add(qcl) + self._edit_session.commit() return qcl diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py old mode 100755 new mode 100644 index c106394..1ad5ff1 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -13,7 +13,7 @@ from odmtools.controller import EditTools from export_service import ExportService from odmtools.lib.Appdirs.appdirs import user_config_dir -from odm2api.ODMconnection import SessionFactory #from odmtools.odmdata.session_factory import SessionFactory +from odmtools.odmdata.session_factory import SessionFactory # tool = LoggerTool() @@ -100,11 +100,12 @@ def add_connection(self, conn_dict): def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) if 'mssql' in connection_string: - s.test_Session().execute("Select top 1 VariableCode From Variables") + s.ms_test_Session().execute("Select top 1 VariableCode From Variables") elif 'mysql' in connection_string: - s.test_Session().execute('Select "VariableCode" From Variables Limit 1') + s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') elif 'postgresql' in connection_string: - s.test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') + s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') return True def test_connection(self, conn_dict): diff --git a/odmtools/view/clsAddPoints.py b/odmtools/view/clsAddPoints.py old mode 100755 new mode 100644 diff --git a/odmtools/view/clsSeriesSelector.py b/odmtools/view/clsSeriesSelector.py old mode 100755 new mode 100644 index fa4a00f..6cd9cdc --- a/odmtools/view/clsSeriesSelector.py +++ b/odmtools/view/clsSeriesSelector.py @@ -1,284 +1,284 @@ -__author__ = 'Jacob' - -import wx -from odmtools.controller.olvSeriesSelector import EVT_OVL_CHECK_EVENT -from odmtools.controller import olvSeriesSelector -from odmtools.odmservices import ServiceManager - -[wxID_PNLSERIESSELECTOR, wxID_PNLSERIESSELECTORCBSITES, wxID_PNLSERIESSELECTORCBVARIABLES, - wxID_PNLSERIESSELECTORCHECKSITE, wxID_PNLSERIESSELECTORCHECKVARIABLE, wxID_PNLSERIESSELECTORLBLSITE, - wxID_PNLSERIESSELECTORLBLVARIABLE, wxID_PNLSERIESSELECTORtableSeries, wxID_PNLSERIESSELECTORPANEL1, - wxID_PNLSERIESSELECTORPANEL2, wxID_PNLSIMPLE, wxID_PNLRADIO, wxID_FRAME1RBADVANCED, wxID_FRAME1RBALL, - wxID_FRAME1RBSIMPLE, wxID_FRAME1SPLITTER, wxID_PNLSPLITTER, wxID_PNLSERIESSELECTORtableSeriesTest, ] = [ - wx.NewId() for _init_ctrls in range(18)] - -class ClsSeriesSelector(wx.Panel): - - def __init__(self, parent, dbservice): - - self.parent = parent - wx.Panel.__init__(self, name=u'pnlSeriesSelector', parent=parent, - size=wx.Size(935, 270), style=wx.TAB_TRAVERSAL) - self._init_ctrls() - self.series_service = dbservice - self.initTableSeries() - self.initSVBoxes() - # Subscribe functions - self.initPubSub() - self.service_manager = ServiceManager() - self.export_service = self.service_manager.get_export_service() - self.selectedIndex = 0 - self.isEditing = False - ## Radio Sizer - ## def _init_coll_boxSizer5_Items(self, parent): - ## # generated method, don't edit - ## - ## parent.AddWindow(self.rbAll, 0, border=1, flag=wx.ALL) - ## parent.AddWindow(self.rbSimple, 0, border=1, flag=wx.ALL) - ## parent.AddWindow(self.rbAdvanced, 0, border=1, flag=wx.ALL) - - - ## Splitter Sizer - def _init_coll_boxSizer3_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.cpnlSimple, 0, flag=wx.RIGHT | wx.LEFT | wx.EXPAND) - parent.AddWindow(self.tblSeries, 100, flag=wx.EXPAND) - - ## Panel Sizer - def _init_coll_boxSizer1_Items(self, parent): - # generated method, don't edit - parent.AddSizer(self.pnlRadio, 0, border=7, flag=wx.LEFT | wx.RIGHT | wx.TOP) - parent.AddWindow(self.pnlData, 100, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Site Sizer - def _init_coll_boxSizer4_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.checkSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.lblSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.cbSites, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Variable Sizer - def _init_coll_boxSizer2_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.checkVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.lblVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.cbVariables, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Simple Filter Sizer - def _init_coll_boxSizer6_Items(self, parent): - parent.AddWindow(self.pnlSite, 50, flag=wx.EXPAND) - parent.AddWindow(self.pnlVar, 50, flag=wx.EXPAND) - # parent.AddSizer(self.boxSizer4, 0, border=5, flag=wx.EXPAND) - # parent.AddSizer(self.boxSizer2, 0, border=5, flag=wx.EXPAND) - - - def _init_sizers(self): - # generated method, don't edit - self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL) - self.boxSizer2 = wx.BoxSizer(orient=wx.HORIZONTAL) - self.boxSizer3 = wx.BoxSizer(orient=wx.VERTICAL) - self.boxSizer4 = wx.BoxSizer(orient=wx.HORIZONTAL) - ## self.boxSizer5 = wx.BoxSizer(orient=wx.HORIZONTAL) - self.boxSizer6 = wx.BoxSizer(orient=wx.VERTICAL) - - self._init_coll_boxSizer1_Items(self.boxSizer1) - self._init_coll_boxSizer2_Items(self.boxSizer2) - self._init_coll_boxSizer3_Items(self.boxSizer3) - self._init_coll_boxSizer4_Items(self.boxSizer4) - ## self._init_coll_boxSizer5_Items(self.boxSizer5) - self._init_coll_boxSizer6_Items(self.boxSizer6) - - self.SetSizer(self.boxSizer1) - ## self.pnlRadio.SetSizer(self.boxSizer5) - ## self.pnlSite.SetSizer(self.boxSizer4) - ## self.pnlVar.SetSizer(self.boxSizer2) - self.cpnlSimple.SetSizer(self.boxSizer6) - self.pnlData.SetSizer(self.boxSizer3) - # self.pnlRadio.SetSizer(self.boxSizer5) - - def _init_ctrls(self): - # generated method, don't edit - - - self.SetClientSize(wx.Size(919, 232)) - self.Enable(True) - - ## Radio panel - self.pnlRadio = wx.Panel(id=wxID_PNLRADIO, name='pnlRadio', parent=self, pos=wx.Point(3, 3), - size=wx.Size(919, 20), style=wx.TAB_TRAVERSAL) - - self.rbAll = wx.RadioButton(id=wxID_FRAME1RBALL, label=u'All', name=u'rbAll', parent=self.pnlRadio, - pos=wx.Point(0, 0), size=wx.Size(81, 20), style=0) - - self.rbSimple = wx.RadioButton(id=wxID_FRAME1RBSIMPLE, label=u'Simple Filter', name=u'rbSimple', - parent=self.pnlRadio, pos=wx.Point(81, 0), size=wx.Size(112, 20), style=0) - - self.rbAdvanced = wx.RadioButton(id=wxID_FRAME1RBADVANCED, label=u'Advanced Filter', name=u'rbAdvanced', - parent=self.pnlRadio, pos=wx.Point(193, 0), size=wx.Size(104, 20), style=0) - - self.rbAll.SetValue(True) - - self.rbAdvanced.Enable(False) - - ## Splitter panel - self.pnlData = wx.Panel(id=wxID_PNLSPLITTER, name='pnlData', parent=self, pos=wx.Point(0, -10), - size=wx.Size(900, 349), style=wx.TAB_TRAVERSAL) - - self.cpnlSimple = wx.CollapsiblePane(self.pnlData, label="", style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) - - ## Site Panel - self.pnlSite = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL1, name='pnlSite', parent=self.cpnlSimple.GetPane(), - pos=wx.Point(3, 0), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) - - self.cbSites = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBSITES, name=u'cbSites', parent=self.pnlSite, - pos=wx.Point(100, 0), size=wx.Size(700, 23), style=wx.CB_READONLY, value=u'') - - self.checkSite = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKSITE, label=u'', name=u'checkSite', - parent=self.pnlSite, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) - - self.lblSite = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLSITE, label=u'Site', name=u'lblSite', - parent=self.pnlSite, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) - self.lblSite.SetToolTipString(u'staticText1') - - self.cbSites.SetLabel(u'') - #self.checkSite.SetValue(False) - - ### Variable Panel - self.pnlVar = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL2, name='pnlVar', parent=self.cpnlSimple.GetPane(), - pos=wx.Point(3, 26), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) - - self.lblVariable = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLVARIABLE, label=u'Variable', name=u'lblVariable', - parent=self.pnlVar, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) - - self.checkVariable = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKVARIABLE, label=u'', name=u'checkVariable', - parent=self.pnlVar, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) - - self.cbVariables = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBVARIABLES, name=u'cbVariables', - parent=self.pnlVar, pos=wx.Point(100, 0), size=wx.Size(700, 25), style=wx.CB_READONLY, - value='comboBox4') - self.cbVariables.SetLabel(u'') - self.cbVariables.Enable(False) - - #wx.EVT_RADIOBUTTON(self, self.rbAll.Id, self.onRbAllRadiobutton) - self.rbAll.Bind(wx.EVT_RADIOBUTTON, self.onRbAllRadiobutton, id=wxID_FRAME1RBALL) - self.rbSimple.Bind(wx.EVT_RADIOBUTTON, self.onRbSimpleRadiobutton, id=wxID_FRAME1RBSIMPLE) - self.rbAdvanced.Bind(wx.EVT_RADIOBUTTON, self.onRbAdvancedRadiobutton, id=wxID_FRAME1RBADVANCED) - - self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onPaneChanged, self.cpnlSimple) - self.checkSite.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKSITE) - self.checkVariable.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKVARIABLE) - self.cbSites.Bind(wx.EVT_COMBOBOX, self.onCbSitesCombobox, id=wxID_PNLSERIESSELECTORCBSITES) - self.cbVariables.Bind(wx.EVT_COMBOBOX, self.onCbVariablesCombobox, id=wxID_PNLSERIESSELECTORCBVARIABLES) - - - ### New Stuff ################################################################################################## - - self.tblSeries = olvSeriesSelector.clsSeriesTable(id=wxID_PNLSERIESSELECTORtableSeries, parent=self.pnlData, - name=u'tblSeries', size=wx.Size(950, 108), pos=wx.Point(5, 5), - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VIRTUAL) - - self.tblSeries.SetEmptyListMsg("No Database Loaded") - - #self.tblSeries.rowFormatter = self._rowFormatter - self.tblSeries.Bind(EVT_OVL_CHECK_EVENT, self.onReadyToPlot) - self.tblSeries.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.getSelectedObject) - self.tblSeries.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnTableRightDown, - id=wxID_PNLSERIESSELECTORtableSeries) - self.tblSeries.handleStandardKeys = True - self.tblSeries.useAlternateBackColors = True - #self.tblSeries.oddRowsBackColor = wx.Colour(143, 188, 188) - self.tblSeries.oddRowsBackColor = wx.Colour(191, 217, 217) - self.cpnlSimple.Collapse(True) - self._init_sizers() - - ## Virtual Event Handlers - def onReadyToPlot(self, event): - event.Skip() - - def onReadyToEdit(self, event): - event.Skip() - - def stopEdit(self): - pass - - def getSelectedObject(self, event): - event.Skip() - - def resetDB(self, dbservice): - pass - - def initTableSeries(self): - pass - - def refreshTableSeries(self, db): - pass - - def refreshSeries(self): - pass - - def initSVBoxes(self): - pass - - def initPubSub(self): - pass - - def OnTableRightDown(self, event): - event.Skip() - - def onPaneChanged(self, event=None): - pass - - def onRbAdvancedRadiobutton(self, event): - event.Skip() - - def onRbAllRadiobutton(self, event): - event.Skip() - - def onRbSimpleRadiobutton(self, event): - event.Skip() - - def onRightPlot(self, event): - event.Skip() - - def onRightEdit(self, event): - event.Skip() - - def onRightRefresh(self, event): - event.Skip() - - def onRightClearSelected(self, event): - event.Skip() - - def onRightExData(self, event): - event.Skip() - - def onRightExMeta(self, event): - event.Skip() - - def onCbSitesCombobox(self, event): - event.Skip() - - def onCbVariablesCombobox(self, event): - event.Skip() - - def siteAndVariables(self): - pass - - def siteOnly(self): - pass - - def variableOnly(self): - pass - - def onCheck(self, event): - event.Skip() - - def setFilter(self, site_code='', var_code='', advfilter=''): - pass - - def isEditing(self): - return self.isEditing - - - def _rowFormatter(self, listItem, object): +__author__ = 'Jacob' + +import wx +from odmtools.controller.olvSeriesSelector import EVT_OVL_CHECK_EVENT +from odmtools.controller import olvSeriesSelector +from odmtools.odmservices import ServiceManager + +[wxID_PNLSERIESSELECTOR, wxID_PNLSERIESSELECTORCBSITES, wxID_PNLSERIESSELECTORCBVARIABLES, + wxID_PNLSERIESSELECTORCHECKSITE, wxID_PNLSERIESSELECTORCHECKVARIABLE, wxID_PNLSERIESSELECTORLBLSITE, + wxID_PNLSERIESSELECTORLBLVARIABLE, wxID_PNLSERIESSELECTORtableSeries, wxID_PNLSERIESSELECTORPANEL1, + wxID_PNLSERIESSELECTORPANEL2, wxID_PNLSIMPLE, wxID_PNLRADIO, wxID_FRAME1RBADVANCED, wxID_FRAME1RBALL, + wxID_FRAME1RBSIMPLE, wxID_FRAME1SPLITTER, wxID_PNLSPLITTER, wxID_PNLSERIESSELECTORtableSeriesTest, ] = [ + wx.NewId() for _init_ctrls in range(18)] + +class ClsSeriesSelector(wx.Panel): + + def __init__(self, parent, dbservice): + + self.parent = parent + wx.Panel.__init__(self, name=u'pnlSeriesSelector', parent=parent, + size=wx.Size(935, 270), style=wx.TAB_TRAVERSAL) + self._init_ctrls() + self.series_service = dbservice + self.initTableSeries() + self.initSVBoxes() + # Subscribe functions + self.initPubSub() + self.service_manager = ServiceManager() + self.export_service = self.service_manager.get_export_service() + self.selectedIndex = 0 + self.isEditing = False + ## Radio Sizer + ## def _init_coll_boxSizer5_Items(self, parent): + ## # generated method, don't edit + ## + ## parent.AddWindow(self.rbAll, 0, border=1, flag=wx.ALL) + ## parent.AddWindow(self.rbSimple, 0, border=1, flag=wx.ALL) + ## parent.AddWindow(self.rbAdvanced, 0, border=1, flag=wx.ALL) + + + ## Splitter Sizer + def _init_coll_boxSizer3_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.cpnlSimple, 0, flag=wx.RIGHT | wx.LEFT | wx.EXPAND) + parent.AddWindow(self.tblSeries, 100, flag=wx.EXPAND) + + ## Panel Sizer + def _init_coll_boxSizer1_Items(self, parent): + # generated method, don't edit + parent.AddSizer(self.pnlRadio, 0, border=7, flag=wx.LEFT | wx.RIGHT | wx.TOP) + parent.AddWindow(self.pnlData, 100, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Site Sizer + def _init_coll_boxSizer4_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.checkSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.lblSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.cbSites, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Variable Sizer + def _init_coll_boxSizer2_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.checkVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.lblVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.cbVariables, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Simple Filter Sizer + def _init_coll_boxSizer6_Items(self, parent): + parent.AddWindow(self.pnlSite, 50, flag=wx.EXPAND) + parent.AddWindow(self.pnlVar, 50, flag=wx.EXPAND) + # parent.AddSizer(self.boxSizer4, 0, border=5, flag=wx.EXPAND) + # parent.AddSizer(self.boxSizer2, 0, border=5, flag=wx.EXPAND) + + + def _init_sizers(self): + # generated method, don't edit + self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL) + self.boxSizer2 = wx.BoxSizer(orient=wx.HORIZONTAL) + self.boxSizer3 = wx.BoxSizer(orient=wx.VERTICAL) + self.boxSizer4 = wx.BoxSizer(orient=wx.HORIZONTAL) + ## self.boxSizer5 = wx.BoxSizer(orient=wx.HORIZONTAL) + self.boxSizer6 = wx.BoxSizer(orient=wx.VERTICAL) + + self._init_coll_boxSizer1_Items(self.boxSizer1) + self._init_coll_boxSizer2_Items(self.boxSizer2) + self._init_coll_boxSizer3_Items(self.boxSizer3) + self._init_coll_boxSizer4_Items(self.boxSizer4) + ## self._init_coll_boxSizer5_Items(self.boxSizer5) + self._init_coll_boxSizer6_Items(self.boxSizer6) + + self.SetSizer(self.boxSizer1) + ## self.pnlRadio.SetSizer(self.boxSizer5) + ## self.pnlSite.SetSizer(self.boxSizer4) + ## self.pnlVar.SetSizer(self.boxSizer2) + self.cpnlSimple.SetSizer(self.boxSizer6) + self.pnlData.SetSizer(self.boxSizer3) + # self.pnlRadio.SetSizer(self.boxSizer5) + + def _init_ctrls(self): + # generated method, don't edit + + + self.SetClientSize(wx.Size(919, 232)) + self.Enable(True) + + ## Radio panel + self.pnlRadio = wx.Panel(id=wxID_PNLRADIO, name='pnlRadio', parent=self, pos=wx.Point(3, 3), + size=wx.Size(919, 20), style=wx.TAB_TRAVERSAL) + + self.rbAll = wx.RadioButton(id=wxID_FRAME1RBALL, label=u'All', name=u'rbAll', parent=self.pnlRadio, + pos=wx.Point(0, 0), size=wx.Size(81, 20), style=0) + + self.rbSimple = wx.RadioButton(id=wxID_FRAME1RBSIMPLE, label=u'Simple Filter', name=u'rbSimple', + parent=self.pnlRadio, pos=wx.Point(81, 0), size=wx.Size(112, 20), style=0) + + self.rbAdvanced = wx.RadioButton(id=wxID_FRAME1RBADVANCED, label=u'Advanced Filter', name=u'rbAdvanced', + parent=self.pnlRadio, pos=wx.Point(193, 0), size=wx.Size(104, 20), style=0) + + self.rbAll.SetValue(True) + + self.rbAdvanced.Enable(False) + + ## Splitter panel + self.pnlData = wx.Panel(id=wxID_PNLSPLITTER, name='pnlData', parent=self, pos=wx.Point(0, -10), + size=wx.Size(900, 349), style=wx.TAB_TRAVERSAL) + + self.cpnlSimple = wx.CollapsiblePane(self.pnlData, label="", style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) + + ## Site Panel + self.pnlSite = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL1, name='pnlSite', parent=self.cpnlSimple.GetPane(), + pos=wx.Point(3, 0), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) + + self.cbSites = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBSITES, name=u'cbSites', parent=self.pnlSite, + pos=wx.Point(100, 0), size=wx.Size(700, 23), style=wx.CB_READONLY, value=u'') + + self.checkSite = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKSITE, label=u'', name=u'checkSite', + parent=self.pnlSite, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) + + self.lblSite = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLSITE, label=u'Site', name=u'lblSite', + parent=self.pnlSite, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) + self.lblSite.SetToolTipString(u'staticText1') + + self.cbSites.SetLabel(u'') + #self.checkSite.SetValue(False) + + ### Variable Panel + self.pnlVar = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL2, name='pnlVar', parent=self.cpnlSimple.GetPane(), + pos=wx.Point(3, 26), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) + + self.lblVariable = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLVARIABLE, label=u'Variable', name=u'lblVariable', + parent=self.pnlVar, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) + + self.checkVariable = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKVARIABLE, label=u'', name=u'checkVariable', + parent=self.pnlVar, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) + + self.cbVariables = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBVARIABLES, name=u'cbVariables', + parent=self.pnlVar, pos=wx.Point(100, 0), size=wx.Size(700, 25), style=wx.CB_READONLY, + value='comboBox4') + self.cbVariables.SetLabel(u'') + self.cbVariables.Enable(False) + + #wx.EVT_RADIOBUTTON(self, self.rbAll.Id, self.onRbAllRadiobutton) + self.rbAll.Bind(wx.EVT_RADIOBUTTON, self.onRbAllRadiobutton, id=wxID_FRAME1RBALL) + self.rbSimple.Bind(wx.EVT_RADIOBUTTON, self.onRbSimpleRadiobutton, id=wxID_FRAME1RBSIMPLE) + self.rbAdvanced.Bind(wx.EVT_RADIOBUTTON, self.onRbAdvancedRadiobutton, id=wxID_FRAME1RBADVANCED) + + self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onPaneChanged, self.cpnlSimple) + self.checkSite.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKSITE) + self.checkVariable.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKVARIABLE) + self.cbSites.Bind(wx.EVT_COMBOBOX, self.onCbSitesCombobox, id=wxID_PNLSERIESSELECTORCBSITES) + self.cbVariables.Bind(wx.EVT_COMBOBOX, self.onCbVariablesCombobox, id=wxID_PNLSERIESSELECTORCBVARIABLES) + + + ### New Stuff ################################################################################################## + + self.tblSeries = olvSeriesSelector.clsSeriesTable(id=wxID_PNLSERIESSELECTORtableSeries, parent=self.pnlData, + name=u'tblSeries', size=wx.Size(950, 108), pos=wx.Point(5, 5), + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VIRTUAL) + + self.tblSeries.SetEmptyListMsg("No Database Loaded") + + #self.tblSeries.rowFormatter = self._rowFormatter + self.tblSeries.Bind(EVT_OVL_CHECK_EVENT, self.onReadyToPlot) + self.tblSeries.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.getSelectedObject) + self.tblSeries.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnTableRightDown, + id=wxID_PNLSERIESSELECTORtableSeries) + self.tblSeries.handleStandardKeys = True + self.tblSeries.useAlternateBackColors = True + #self.tblSeries.oddRowsBackColor = wx.Colour(143, 188, 188) + self.tblSeries.oddRowsBackColor = wx.Colour(191, 217, 217) + self.cpnlSimple.Collapse(True) + self._init_sizers() + + ## Virtual Event Handlers + def onReadyToPlot(self, event): + event.Skip() + + def onReadyToEdit(self, event): + event.Skip() + + def stopEdit(self): + pass + + def getSelectedObject(self, event): + event.Skip() + + def resetDB(self, dbservice): + pass + + def initTableSeries(self): + pass + + def refreshTableSeries(self, db): + pass + + def refreshSeries(self): + pass + + def initSVBoxes(self): + pass + + def initPubSub(self): + pass + + def OnTableRightDown(self, event): + event.Skip() + + def onPaneChanged(self, event=None): + pass + + def onRbAdvancedRadiobutton(self, event): + event.Skip() + + def onRbAllRadiobutton(self, event): + event.Skip() + + def onRbSimpleRadiobutton(self, event): + event.Skip() + + def onRightPlot(self, event): + event.Skip() + + def onRightEdit(self, event): + event.Skip() + + def onRightRefresh(self, event): + event.Skip() + + def onRightClearSelected(self, event): + event.Skip() + + def onRightExData(self, event): + event.Skip() + + def onRightExMeta(self, event): + event.Skip() + + def onCbSitesCombobox(self, event): + event.Skip() + + def onCbVariablesCombobox(self, event): + event.Skip() + + def siteAndVariables(self): + pass + + def siteOnly(self): + pass + + def variableOnly(self): + pass + + def onCheck(self, event): + event.Skip() + + def setFilter(self, site_code='', var_code='', advfilter=''): + pass + + def isEditing(self): + return self.isEditing + + + def _rowFormatter(self, listItem, object): pass \ No newline at end of file From 45f6233acbbebaff3cde23dfbd15b929c2466604 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 22 Sep 2016 17:56:40 -0600 Subject: [PATCH 016/158] #293 Updated the create methods to use the ODM2 create methods Unit test pass. Only method that has not been updated is the create_qualifier()' --- odmtools/odmdata/__init__.py | 2 +- odmtools/odmdata/session_factory.py | 114 +++++++++--------- odmtools/odmservices/cv_service.py | 7 +- odmtools/odmservices/series_service.py | 37 +++--- odmtools/odmservices/service_manager.py | 9 +- tests/test_odmservices/test_cv_service.py | 6 +- tests/test_odmservices/test_edit_service.py | 2 +- tests/test_odmservices/test_export_service.py | 2 +- tests/test_odmservices/test_series_service.py | 2 +- 9 files changed, 86 insertions(+), 95 deletions(-) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index dfb89a2..4e1285a 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -14,7 +14,7 @@ from sample_medium_cv import SampleMediumCV from sample_type_cv import SampleTypeCV from series import Series -from session_factory import SessionFactory +from odm2api.ODMconnection import SessionFactory from site import Site from site_type_cv import SiteTypeCV from source import Source diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py index 0d2d488..8afda48 100644 --- a/odmtools/odmdata/session_factory.py +++ b/odmtools/odmdata/session_factory.py @@ -1,57 +1,57 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - - -class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_size=20) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - - ''' - # Removing pool_timeout and max_overflow allowed the tests to pass - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, pool_size=20, max_overflow=0) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - ''' - - ''' - # Old code - class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, - #pool_size=20, - pool_recycle=3600) - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - ''' - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) - self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) - self.my_test_Session = sessionmaker(bind=self.my_test_engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - +# from sqlalchemy import create_engine +# from sqlalchemy.orm import sessionmaker +# +# +# class SessionFactory(): +# def __init__(self, connection_string, echo): +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_size=20) +# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'connect_timeout': 1}) +# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'timeout': 1}) +# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# connect_args={'connect_timeout': 1}) +# +# ''' +# # Removing pool_timeout and max_overflow allowed the tests to pass +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, pool_size=20, max_overflow=0) +# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) +# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) +# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, +# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) +# ''' +# +# ''' +# # Old code +# class SessionFactory(): +# def __init__(self, connection_string, echo): +# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, +# #pool_size=20, +# pool_recycle=3600) +# +# # Create session maker +# self.Session = sessionmaker(bind=self.engine) +# +# def get_session(self): +# return self.Session() +# +# def __repr__(self): +# return "" % (self.engine) +# ''' +# +# # Create session maker +# self.Session = sessionmaker(bind=self.engine) +# self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) +# self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) +# self.my_test_Session = sessionmaker(bind=self.my_test_engine) +# +# def get_session(self): +# return self.Session() +# +# def __repr__(self): +# return "" % (self.engine) +# diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index 24b5e81..c6c9a42 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -16,14 +16,17 @@ from odmtools.odmdata import Qualifier from odmtools.odmdata import Unit from sqlalchemy import not_ +from odm2api.ODM2.services.readService import ReadODM2 -class CVService(): +class CVService(): # Rename to ReadService # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() + self._edit_session = self._session_factory.getSession() self._debug = debug + self.read_service = ReadODM2(self._session_factory, debug=self._debug) + # Controlled Vocabulary get methods diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 078e6fe..e3d5716 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -18,20 +18,23 @@ from odmtools.odmdata import ODMVersion from odmtools.common.logger import LoggerTool import pandas as pd +from odm2api.ODM2.services.createService import CreateODM2 + # tool = LoggerTool() # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) logger =logging.getLogger('main') -class SeriesService(): +class SeriesService(): # Rename to CreateService # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() + self._edit_session = self._session_factory.getSession() self._debug = debug + self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks + self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks def get_db_version(self): return self._edit_session.query(ODMVersion).first().version_number @@ -534,7 +537,7 @@ def save_values(self, values): def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): """ - + series -> Result in ODM2 :param data_values: :param site_id: :param variable_id: @@ -551,13 +554,10 @@ def create_new_series(self, data_values, site_id, variable_id, method_id, source series.source_id = source_id series.quality_control_level_id = qcl_id - self._edit_session.add(series) - self._edit_session.commit() - return series + return self.create_service.createResult(series) def create_method(self, description, link): """ - :param description: :param link: :return: @@ -567,29 +567,22 @@ def create_method(self, description, link): if link is not None: meth.link = link - self._edit_session.add(meth) - self._edit_session.commit() + self.create_service.createMethod(method=meth) return meth def create_variable_by_var(self, var): """ - :param var: Variable Object :return: """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None + self.create_service.createVariable(var=var) + return var def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, general_category, no_data_value): """ - :param code: :param name: :param speciation: @@ -618,13 +611,12 @@ def create_variable( var.general_category = general_category var.no_data_value = no_data_value - self._edit_session.add(var) - self._edit_session.commit() + self.create_service.createVariable(var=var) return var def create_qcl(self, code, definition, explanation): """ - + qcl -> Processing Level in ODM2 :param code: :param definition: :param explanation: @@ -634,9 +626,8 @@ def create_qcl(self, code, definition, explanation): qcl.code = code qcl.definition = definition qcl.explanation = explanation + self.create_service.createProcessingLevel(proclevel=qcl) - self._edit_session.add(qcl) - self._edit_session.commit() return qcl diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 1ad5ff1..142a5c0 100644 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -13,7 +13,8 @@ from odmtools.controller import EditTools from export_service import ExportService from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata.session_factory import SessionFactory +# from odmtools.odmdata.session_factory import SessionFactory +from odm2api.ODMconnection import SessionFactory # tool = LoggerTool() @@ -100,12 +101,12 @@ def add_connection(self, conn_dict): def testEngine(self, connection_string): s = SessionFactory(connection_string, echo=False) if 'mssql' in connection_string: - s.ms_test_Session().execute("Select top 1 VariableCode From Variables") + s.test_Session().execute("Select top 1 VariableCode From Variables") elif 'mysql' in connection_string: - s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') + s.test_Session().execute('Select "VariableCode" From Variables Limit 1') elif 'postgresql' in connection_string: #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + s.test_Session().execute('Select "VariableCode" From "Variables" Limit 1') return True def test_connection(self, conn_dict): diff --git a/tests/test_odmservices/test_cv_service.py b/tests/test_odmservices/test_cv_service.py index 5bfd72a..61c8ef5 100644 --- a/tests/test_odmservices/test_cv_service.py +++ b/tests/test_odmservices/test_cv_service.py @@ -1,8 +1,4 @@ -import pytest -import sqlalchemy.orm.exc -from odmtools.odmdata import Qualifier from odmtools.odmservices import CVService - from tests import test_util @@ -13,7 +9,7 @@ class TestCVService: def setup(self): self.connection_string = "sqlite:///:memory:" self.cv_service = CVService(self.connection_string, debug=False) - self.session = self.cv_service._session_factory.get_session() + self.session = self.cv_service._session_factory.getSession() engine = self.cv_service._session_factory.engine test_util.build_db(engine) diff --git a/tests/test_odmservices/test_edit_service.py b/tests/test_odmservices/test_edit_service.py index 879113d..f8fc913 100644 --- a/tests/test_odmservices/test_edit_service.py +++ b/tests/test_odmservices/test_edit_service.py @@ -15,7 +15,7 @@ def setup(self): self.memory_database = MemoryDatabase() self.memory_database.set_series_service(self.series_service) - self.session = self.memory_database.series_service._session_factory.get_session() + self.session = self.memory_database.series_service._session_factory.getSession() self.series = test_util.add_series_bulk_data(self.session) #assert len(self.series.data_values) == 100 diff --git a/tests/test_odmservices/test_export_service.py b/tests/test_odmservices/test_export_service.py index acc146f..89e7d58 100644 --- a/tests/test_odmservices/test_export_service.py +++ b/tests/test_odmservices/test_export_service.py @@ -8,7 +8,7 @@ class TestExportService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() + self.session = self.series_service._session_factory.getSession() engine = self.series_service._session_factory.engine test_util.build_db(engine) self.series = test_util.add_series(self.session) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 5cf0e81..96c1afb 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -8,7 +8,7 @@ class TestSeriesService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() + self.session = self.series_service._session_factory.getSession() engine = self.series_service._session_factory.engine test_util.build_db(engine) """ From 55e5721b5d7c0291d05665a251e5ad8154291cc8 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 28 Sep 2016 11:41:53 -0600 Subject: [PATCH 017/158] Finished the create services, and began working on the get --- odmtools/controller/logicEditTools.py | 2 +- odmtools/gui/frmFlagValues.py | 2 +- odmtools/odmservices/cv_service.py | 8 ++++++ odmtools/odmservices/edit_service.py | 2 +- odmtools/odmservices/series_service.py | 27 ++++++++++--------- tests/test_odmservices/test_series_service.py | 18 ++++++------- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index dd0f813..ef81058 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -380,7 +380,7 @@ def create_qcl(self, code, definition, explanation): return qcl def create_qualifer(self, code, description): - qual = self._edit_service.create_qualifier(code, description) + qual = self._edit_service.create_annotation(code, description) if self._record: self._script('new_qual = series_service.get_qualifier_by_code(%s)\n' % (qual.code)) Publisher.sendMessage("scroll") diff --git a/odmtools/gui/frmFlagValues.py b/odmtools/gui/frmFlagValues.py index 32e4286..73f7ced 100644 --- a/odmtools/gui/frmFlagValues.py +++ b/odmtools/gui/frmFlagValues.py @@ -151,7 +151,7 @@ def OnBtnOKButton(self, event): q.description = desc ''' - q=self.series_service.create_qualifier(code, desc) + q=self.series_service.create_annotation(code, desc) self.qid = q.id self.selectedValue = q.code + '-' + q.description diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index c6c9a42..3466d0c 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -99,3 +99,11 @@ def get_unit_by_name(self, unit_name): def get_unit_by_id(self, unit_id): result = self._edit_session.query(Unit).filter_by(id=unit_id).first() return result + + def get_annotation_by_code(self, code): + self.read_service.getAnnotations(type=code) + return + + def get_all_annotations(self): + return self.read_service.getAnnotations(type=None) + diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 403f07d..8431f54 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -589,7 +589,7 @@ def create_method(self, description, link): return self.memDB.series_service.create_method(description, link) def create_qualifier(self, code, definition): - return self.memDB.series_service.create_qualifier(code, definition) + return self.memDB.series_service.create_annotation(code, definition) def create_variable(self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, general_category, no_data_value): diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index e3d5716..c27158b 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -19,6 +19,8 @@ from odmtools.common.logger import LoggerTool import pandas as pd from odm2api.ODM2.services.createService import CreateODM2 +from odm2api.ODM2.models import Annotations +from odm2api.ODM2.services.readService import ReadODM2 # tool = LoggerTool() @@ -32,6 +34,7 @@ def __init__(self, connection_string="", debug=False): self._edit_session = self._session_factory.getSession() self._debug = debug self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) + self.read_service = ReadODM2(self._session_factory, debug=self._debug) def reset_session(self): self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks @@ -191,8 +194,9 @@ def get_all_qualifiers(self): :return: List[Qualifiers] """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result + # result = self._edit_session.query(Annotations).order_by(Annotations.AnnotationCode).all() + # return result + return self.read_service.getAnnotations(None) def get_qualifier_by_code(self, code): """ @@ -630,24 +634,23 @@ def create_qcl(self, code, definition, explanation): return qcl + def create_annotation_by_anno(self, annotation): + self.create_service.create(annotation) + return annotation - def create_qualifier_by_qual(self, qualifier): - self._edit_session.add(qualifier) - self._edit_session.commit() - return qualifier - - def create_qualifier(self, code, description): + def create_annotation(self, code, description): """ :param code: :param description: :return: """ - qual = Qualifier() - qual.code = code - qual.description = description + annotation = Annotations() + annotation.AnnotationCode = code + annotation.AnnotationText = description + annotation.AnnotationTypeCV = "timeSeriesResultValueAnnotation" - return self.create_qualifier_by_qual(qual) + return self.create_annotation_by_anno(annotation) ##################### # diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 96c1afb..1b4eafa 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -1,5 +1,6 @@ from odmtools.odmdata import * from odmtools.odmservices import SeriesService +from odm2api.ODM2.models import Annotations from tests import test_util @@ -62,32 +63,31 @@ def test_get_all_sites_empty(self): #assert len(sites) == 0 assert sites is None - def test_create_qualifier(self): + def test_create_annotation(self): qual = Qualifier() qual.code = "ABC123" qual.description = "This is a test" - self.series_service.create_qualifier_by_qual(qual) + self.series_service.create_annotation_by_anno(qual) assert qual.id is not None - def test_get_qualifier_by_code(self): - assert self.series_service.get_all_qualifiers() == [] + def test_get_annotation_by_code(self): + assert self.series_service.get_all_qualifiers() == None - qual= self.series_service.create_qualifier("ABC123","This is a test") + qual = self.series_service.create_annotation("ABC123", "This is a test") db_qual = self.series_service.get_qualifier_by_code("ABC123") assert qual.id == db_qual.id - def test_get_qualifiers(self): - assert self.series_service.get_all_qualifiers() == [] + def test_get_annotation(self): + assert self.series_service.get_all_qualifiers() == None - qual= self.series_service.create_qualifier("ABC123","This is a test") + qual= self.series_service.create_annotation("ABC123", "This is a test") db_qual = self.series_service.get_all_qualifiers()[0] assert qual.id == db_qual.id - def test_get_all_sites(self): assert self.series_service.get_used_sites() is None From 5803a170cb92d0db5c88e01a38a4d809ef29da2d Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 1 Oct 2016 12:22:05 -0600 Subject: [PATCH 018/158] Updated the view on the wizard method page Going to continue to update all the views than the controllers --- odmtools/gui/pageMethod.py | 300 ++++++++++++++++++++++--------------- odmtools/gui/wizSave.py | 79 +++++----- 2 files changed, 228 insertions(+), 151 deletions(-) diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py index df72837..1665c33 100644 --- a/odmtools/gui/pageMethod.py +++ b/odmtools/gui/pageMethod.py @@ -16,120 +16,186 @@ # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) logger =logging.getLogger('main') - -class pnlMethod(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLMETHOD, name=u'pnlMethod', - parent=prnt, pos=wx.Point(135, 307), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbGenerate = wx.RadioButton(id=wxID_PNLMETHODSRBGENERATE, - label=u'Automatically generate a Method ', name=u'rbGenerate', - parent=self, pos=wx.Point(16, 8), size=wx.Size(392, 16), style=0) - self.rbGenerate.SetValue(True) - self.rbGenerate.Bind(wx.EVT_RADIOBUTTON, self.OnRbGenerateRadiobutton, - id=wxID_PNLMETHODSRBGENERATE) - - self.rbSelect = wx.RadioButton(id=wxID_PNLMETHODSRBSELECT, - label=u'Select an existing Method', name=u'rbSelect', parent=self, - pos=wx.Point(16, 32), size=wx.Size(392, 13), style=0) - self.rbSelect.SetValue(False) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLMETHODSRBSELECT) - - self.rbCreateNew = wx.RadioButton(id=wxID_PNLMETHODSRBCREATENEW, - label=u'Create a new Method ', name=u'rbCreateNew', parent=self, - pos=wx.Point(16, 208), size=wx.Size(392, 13), style=0) - self.rbCreateNew.SetValue(False) - self.rbCreateNew.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateNewRadiobutton, - id=wxID_PNLMETHODSRBCREATENEW) - - self.txtMethodDescrip = wx.richtext.RichTextCtrl(id=wxID_PNLMETHODSRICHTEXTCTRL1, - parent=self, pos=wx.Point(16, 224), size=wx.Size(392, 84), - style=wx.richtext.RE_MULTILINE, value=u'Method Description') - self.txtMethodDescrip.Enable(False) - self.txtMethodDescrip.Bind(wx.EVT_SET_FOCUS, self.OnTxtMethodDescripSetFocus) - self.txtMethodDescrip.Bind(wx.EVT_KILL_FOCUS, self.OnTxtMethodDescripKillFocus) - - self.lstMethods = wx.ListCtrl(id=wxID_PNLMETHODSLISTCTRL1, - name='lstMethods', parent=self, pos=wx.Point(16, 48), - size=wx.Size(392, 152), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstMethods.Bind(wx.EVT_SET_FOCUS, self.OnLstMethodSetFocus) - - - self.lstMethods.InsertColumn(0, 'Description') - self.lstMethods.InsertColumn(1, 'Link') - self.lstMethods.InsertColumn(2, 'id') - self.lstMethods.SetColumnWidth(0, 200) - self.lstMethods.SetColumnWidth(1, 153) - self.lstMethods.SetColumnWidth(2,0) - # self.lstMethods.Enable(False) - - - - - def __init__(self, parent, id, pos, size, style, name, ss, method): - self.series_service = ss - self.prev_val = method - self._init_ctrls(parent) - - def OnLstMethodSetFocus(self, event): - self.rbSelect.SetValue(True) - - def OnRbGenerateRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbSelectRadiobutton(self, event): - self.lstMethods.Enable(True) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbCreateNewRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(True) - - event.Skip() - - def OnTxtMethodDescripSetFocus(self, event): - if self.txtMethodDescrip.GetValue() =="Method Description": - self.txtMethodDescrip.SetValue("") - - event.Skip() - - def OnTxtMethodDescripKillFocus(self, event): - if self.txtMethodDescrip.GetValue() =="": - self.txtMethodDescrip.SetValue("Method Description") - - event.Skip() - - - def getMethod(self): - - m = Method() - if self.rbGenerate.Value: - genmethod = "Values derived from ODM Tools Python" - m= self.series_service.get_method_by_description(genmethod) - if m is None: - logger.debug("assigning new method description") - m = Method() - m.description = genmethod - - elif self.rbSelect.Value: - index = self.lstMethods.GetFirstSelected() - desc= self.lstMethods.GetItem(index, 0).GetText() - - logger.debug(desc) - m= self.series_service.get_method_by_description(desc) - - - - elif self.rbCreateNew.Value: - logger.debug("assigning new method description") - m.description = self.txtMethodDescrip.GetValue() - return m \ No newline at end of file +class pnlMethod(wx.Panel): # Rename this to page method view + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Method") + static_line = wx.StaticLine(self, size=(-1, 12)) + + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + method_code_text = wx.StaticText(self, label="Method Code") + self.method_code_text_ctrl = wx.TextCtrl(self) + method_name_text = wx.StaticText(self, label="Method Name") + self.method_name_text_ctrl = wx.TextCtrl(self) + method_type_text = wx.StaticText(self, label="Method Type") + self.method_type_combo = wx.ComboBox(self) + + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + organization_text = wx.StaticText(self, label="Organization") + self.organization_text_ctrl = wx.TextCtrl(self) + method_link_text = wx.StaticText(self, label="Method Link") + self.method_link_text_ctrl = wx.TextCtrl(self) + description_text = wx.StaticText(self, label="Description") + self.description_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_code_text, 0, wx.EXPAND) + row_sizer.Add(self.method_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 24) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_name_text, 0, wx.EXPAND) + row_sizer.Add(self.method_name_text_ctrl, 1, wx.EXPAND | wx.LEFT, 20) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_type_text, 0, wx.EXPAND) + row_sizer.Add(self.method_type_combo, 1, wx.EXPAND | wx.LEFT, 26) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(organization_text, 0, wx.EXPAND) + row_sizer.Add(self.organization_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_link_text, 0, wx.EXPAND) + row_sizer.Add(self.method_link_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(description_text, 0, wx.EXPAND) + row_sizer.Add(self.description_text_ctrl, 1, wx.EXPAND | wx.LEFT, 34) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + # Style Components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + # Add components to sizer + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) + +# class pnlMethod(wx.Panel): +# def _init_ctrls(self, prnt): +# # generated method, don't edit +# wx.Panel.__init__(self, id=wxID_PNLMETHOD, name=u'pnlMethod', +# parent=prnt, pos=wx.Point(135, 307), size=wx.Size(439, 357), +# style=wx.TAB_TRAVERSAL) +# self.SetClientSize(wx.Size(423, 319)) +# +# self.rbGenerate = wx.RadioButton(id=wxID_PNLMETHODSRBGENERATE, +# label=u'Automatically generate a Method ', name=u'rbGenerate', +# parent=self, pos=wx.Point(16, 8), size=wx.Size(392, 16), style=0) +# self.rbGenerate.SetValue(True) +# self.rbGenerate.Bind(wx.EVT_RADIOBUTTON, self.OnRbGenerateRadiobutton, +# id=wxID_PNLMETHODSRBGENERATE) +# +# self.rbSelect = wx.RadioButton(id=wxID_PNLMETHODSRBSELECT, +# label=u'Select an existing Method', name=u'rbSelect', parent=self, +# pos=wx.Point(16, 32), size=wx.Size(392, 13), style=0) +# self.rbSelect.SetValue(False) +# self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, +# id=wxID_PNLMETHODSRBSELECT) +# +# self.rbCreateNew = wx.RadioButton(id=wxID_PNLMETHODSRBCREATENEW, +# label=u'Create a new Method ', name=u'rbCreateNew', parent=self, +# pos=wx.Point(16, 208), size=wx.Size(392, 13), style=0) +# self.rbCreateNew.SetValue(False) +# self.rbCreateNew.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateNewRadiobutton, +# id=wxID_PNLMETHODSRBCREATENEW) +# +# self.txtMethodDescrip = wx.richtext.RichTextCtrl(id=wxID_PNLMETHODSRICHTEXTCTRL1, +# parent=self, pos=wx.Point(16, 224), size=wx.Size(392, 84), +# style=wx.richtext.RE_MULTILINE, value=u'Method Description') +# self.txtMethodDescrip.Enable(False) +# self.txtMethodDescrip.Bind(wx.EVT_SET_FOCUS, self.OnTxtMethodDescripSetFocus) +# self.txtMethodDescrip.Bind(wx.EVT_KILL_FOCUS, self.OnTxtMethodDescripKillFocus) +# +# self.lstMethods = wx.ListCtrl(id=wxID_PNLMETHODSLISTCTRL1, +# name='lstMethods', parent=self, pos=wx.Point(16, 48), +# size=wx.Size(392, 152), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) +# self.lstMethods.Bind(wx.EVT_SET_FOCUS, self.OnLstMethodSetFocus) +# +# +# self.lstMethods.InsertColumn(0, 'Description') +# self.lstMethods.InsertColumn(1, 'Link') +# self.lstMethods.InsertColumn(2, 'id') +# self.lstMethods.SetColumnWidth(0, 200) +# self.lstMethods.SetColumnWidth(1, 153) +# self.lstMethods.SetColumnWidth(2,0) +# # self.lstMethods.Enable(False) +# +# +# +# +# def __init__(self, parent, id, pos, size, style, name, ss, method): +# self.series_service = ss +# self.prev_val = method +# self._init_ctrls(parent) +# +# def OnLstMethodSetFocus(self, event): +# self.rbSelect.SetValue(True) +# +# def OnRbGenerateRadiobutton(self, event): +# # self.lstMethods.Enable(False) +# self.txtMethodDescrip.Enable(False) +# +# event.Skip() +# +# def OnRbSelectRadiobutton(self, event): +# self.lstMethods.Enable(True) +# self.txtMethodDescrip.Enable(False) +# +# event.Skip() +# +# def OnRbCreateNewRadiobutton(self, event): +# # self.lstMethods.Enable(False) +# self.txtMethodDescrip.Enable(True) +# +# event.Skip() +# +# def OnTxtMethodDescripSetFocus(self, event): +# if self.txtMethodDescrip.GetValue() =="Method Description": +# self.txtMethodDescrip.SetValue("") +# +# event.Skip() +# +# def OnTxtMethodDescripKillFocus(self, event): +# if self.txtMethodDescrip.GetValue() =="": +# self.txtMethodDescrip.SetValue("Method Description") +# +# event.Skip() +# +# +# def getMethod(self): +# +# m = Method() +# if self.rbGenerate.Value: +# genmethod = "Values derived from ODM Tools Python" +# m= self.series_service.get_method_by_description(genmethod) +# if m is None: +# logger.debug("assigning new method description") +# m = Method() +# m.description = genmethod +# +# elif self.rbSelect.Value: +# index = self.lstMethods.GetFirstSelected() +# desc= self.lstMethods.GetItem(index, 0).GetText() +# +# logger.debug(desc) +# m= self.series_service.get_method_by_description(desc) +# +# +# +# elif self.rbCreateNew.Value: +# logger.debug("assigning new method description") +# m.description = self.txtMethodDescrip.GetValue() +# return m \ No newline at end of file diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 85e6b0a..c066a5e 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -108,41 +108,51 @@ def _init_data(self, series_service): ######################################################################## -class MethodPage(wiz.WizardPageSimple): - def __init__(self, parent, title, series_service, method): - """Constructor""" +class MethodPage(wiz.WizardPageSimple): # Raname this page to page method controller + def __init__(self, parent): + # pageMethod.pnlMethod.__init__(self, parent) wiz.WizardPageSimple.__init__(self, parent) - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.method = method - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageMethod.pnlMethod(self, id=wxID_PNLMETHOD, name=u'pnlMethod', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, ss=series_service, method=method) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series): - meth = series.get_all_methods() - index = 0 - for m, i in zip(meth, range(len(meth))): - num_items = self.panel.lstMethods.GetItemCount() - self.panel.lstMethods.InsertStringItem(num_items, str(m.description)) - self.panel.lstMethods.SetStringItem(num_items, 1, str(m.link)) - self.panel.lstMethods.SetStringItem(num_items, 2, str(m.id)) - - if m.description == self.method.description: - index = i - - self.panel.lstMethods.Focus(index) - self.panel.lstMethods.Select(index) + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.page_method_view = pageMethod.pnlMethod(self) + main_sizer.Add(self.page_method_view, 1, wx.EXPAND | wx.ALL, 0) + self.SetSizer(main_sizer) + +# class MethodPage(wiz.WizardPageSimple): +# def __init__(self, parent, title, series_service, method): +# """Constructor""" +# wiz.WizardPageSimple.__init__(self, parent) +# +# sizer = wx.BoxSizer(wx.VERTICAL) +# self.sizer = sizer +# self.SetSizer(sizer) +# self.method = method +# +# title = wx.StaticText(self, -1, title) +# title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) +# sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) +# sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) +# self.panel = pageMethod.pnlMethod(self, id=wxID_PNLMETHOD, name=u'pnlMethod', +# pos=wx.Point(536, 285), size=wx.Size(439, 357), +# style=wx.TAB_TRAVERSAL, ss=series_service, method=method) +# self.sizer.Add(self.panel, 1, wx.EXPAND, 5) +# +# self._init_data(self.panel.series_service) +# +# def _init_data(self, series): +# meth = series.get_all_methods() +# index = 0 +# for m, i in zip(meth, range(len(meth))): +# num_items = self.panel.lstMethods.GetItemCount() +# self.panel.lstMethods.InsertStringItem(num_items, str(m.description)) +# self.panel.lstMethods.SetStringItem(num_items, 1, str(m.link)) +# self.panel.lstMethods.SetStringItem(num_items, 2, str(m.id)) +# +# if m.description == self.method.description: +# index = i +# +# self.panel.lstMethods.Focus(index) +# self.panel.lstMethods.Select(index) ######################################################################## @@ -246,7 +256,8 @@ def __init__(self, parent, service_manager, record_service): self.currSeries = record_service.get_series() self.pgIntro = pageIntro.pageIntro(self, "Intro") - self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) + # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) + self.pgMethod = MethodPage(self) self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) From 0e2dba8127969e2bba61d44d980416161a88c3b6 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 1 Oct 2016 12:56:34 -0600 Subject: [PATCH 019/158] Updated the qcl view on the wizard Updated the name to Processing Level. Parse the code into view/contrller and into their own files --- odmtools/controller/WizardMethodController.py | 13 ++++ .../WizardProcessLevelController.py | 13 ++++ odmtools/gui/wizSave.py | 9 ++- odmtools/view/WizardMethodView.py | 68 +++++++++++++++++++ odmtools/view/WizardProcessLevelView.py | 45 ++++++++++++ 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 odmtools/controller/WizardMethodController.py create mode 100644 odmtools/controller/WizardProcessLevelController.py create mode 100644 odmtools/view/WizardMethodView.py create mode 100644 odmtools/view/WizardProcessLevelView.py diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py new file mode 100644 index 0000000..60dee6f --- /dev/null +++ b/odmtools/controller/WizardMethodController.py @@ -0,0 +1,13 @@ +import wx +from wx.wizard import WizardPageSimple +from odmtools.view.WizardMethodView import WizardMethodView + + +class WizardMethodController(WizardPageSimple): + def __init__(self, parent): + WizardPageSimple.__init__(self, parent) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.method_view = WizardMethodView(self) + main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.ALL, 0) + self.SetSizer(main_sizer) \ No newline at end of file diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py new file mode 100644 index 0000000..e33d9b4 --- /dev/null +++ b/odmtools/controller/WizardProcessLevelController.py @@ -0,0 +1,13 @@ +import wx +from odmtools.view.WizardProcessLevelView import WizardProcessLevelView +from wx.wizard import WizardPageSimple + + +class WizardProcessLevelController(WizardPageSimple): + def __init__(self, parent): + WizardPageSimple.__init__(self, parent) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.method_view = WizardProcessLevelView(self) + main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.ALL, 0) + self.SetSizer(main_sizer) \ No newline at end of file diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index c066a5e..b950d18 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -209,6 +209,9 @@ def fill_summary(self): ######################################################################## +from odmtools.controller.WizardMethodController import WizardMethodController +from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController + class wizSave(wx.wizard.Wizard): def _init_ctrls(self, prnt): # generated method, don't edit @@ -257,8 +260,10 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro = pageIntro.pageIntro(self, "Intro") # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - self.pgMethod = MethodPage(self) - self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) + # self.pgMethod = MethodPage(self) + self.pgMethod = WizardMethodController(self) + # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) + self.pgQCL = WizardProcessLevelController(self) self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) self.pgSummary = SummaryPage(self, "Summary", self.series_service) diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py new file mode 100644 index 0000000..1891c66 --- /dev/null +++ b/odmtools/view/WizardMethodView.py @@ -0,0 +1,68 @@ +import wx + + +class WizardMethodView(wx.Panel): # Rename this to page method view + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Method") + static_line = wx.StaticLine(self, size=(-1, 12)) + + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + method_code_text = wx.StaticText(self, label="Method Code") + self.method_code_text_ctrl = wx.TextCtrl(self) + method_name_text = wx.StaticText(self, label="Method Name") + self.method_name_text_ctrl = wx.TextCtrl(self) + method_type_text = wx.StaticText(self, label="Method Type") + self.method_type_combo = wx.ComboBox(self) + + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + organization_text = wx.StaticText(self, label="Organization") + self.organization_text_ctrl = wx.TextCtrl(self) + method_link_text = wx.StaticText(self, label="Method Link") + self.method_link_text_ctrl = wx.TextCtrl(self) + description_text = wx.StaticText(self, label="Description") + self.description_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style Components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + # Add components to sizer + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_code_text, 0, wx.EXPAND) + row_sizer.Add(self.method_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 24) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_name_text, 0, wx.EXPAND) + row_sizer.Add(self.method_name_text_ctrl, 1, wx.EXPAND | wx.LEFT, 20) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_type_text, 0, wx.EXPAND) + row_sizer.Add(self.method_type_combo, 1, wx.EXPAND | wx.LEFT, 26) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(organization_text, 0, wx.EXPAND) + row_sizer.Add(self.organization_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_link_text, 0, wx.EXPAND) + row_sizer.Add(self.method_link_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(description_text, 0, wx.EXPAND) + row_sizer.Add(self.description_text_ctrl, 1, wx.EXPAND | wx.LEFT, 34) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) \ No newline at end of file diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py new file mode 100644 index 0000000..475d75a --- /dev/null +++ b/odmtools/view/WizardProcessLevelView.py @@ -0,0 +1,45 @@ +import wx + + +class WizardProcessLevelView(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Processing Level") + static_line = wx.StaticLine(self, size=(-1, 12)) + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + level_code = wx.StaticText(self, label="Processing Level Code") + self.level_code_text_ctrl = wx.TextCtrl(self) + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + definition_text = wx.StaticText(self, label="Definition") + self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + explanation_text = wx.StaticText(self, label="Explanation") + self.explanation_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + # Add components to sizer + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(level_code, 0, wx.EXPAND) + row_sizer.Add(self.level_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(definition_text, 0, wx.EXPAND) + row_sizer.Add(self.definition_text_ctrl, 1, wx.EXPAND | wx.LEFT, 95) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(explanation_text, 0, wx.EXPAND) + row_sizer.Add(self.explanation_text_ctrl, 1, wx.EXPAND | wx.LEFT, 85) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) From e7acd05237d092a875327112e7e14833d340e69b Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 1 Oct 2016 13:14:48 -0600 Subject: [PATCH 020/158] Updated the variable page in the wizard --- .../WizardProcessLevelController.py | 4 +- .../controller/WizardVariableController.py | 13 ++++ odmtools/gui/wizSave.py | 14 +++- odmtools/view/WizardVariableView.py | 68 +++++++++++++++++++ 4 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 odmtools/controller/WizardVariableController.py create mode 100644 odmtools/view/WizardVariableView.py diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index e33d9b4..6555e10 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -8,6 +8,6 @@ def __init__(self, parent): WizardPageSimple.__init__(self, parent) main_sizer = wx.BoxSizer(wx.VERTICAL) - self.method_view = WizardProcessLevelView(self) - main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.ALL, 0) + self.processing_level_view = WizardProcessLevelView(self) + main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.ALL, 0) self.SetSizer(main_sizer) \ No newline at end of file diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py new file mode 100644 index 0000000..8b0579d --- /dev/null +++ b/odmtools/controller/WizardVariableController.py @@ -0,0 +1,13 @@ +import wx +from odmtools.view.WizardVariableView import WizardVariableView +from wx.wizard import WizardPageSimple + + +class WizardVariableController(WizardPageSimple): + def __init__(self, parent): + WizardPageSimple.__init__(self, parent) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.variable_view = WizardVariableView(self) + main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.ALL, 0) + self.SetSizer(main_sizer) \ No newline at end of file diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index b950d18..957e5ef 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -25,6 +25,8 @@ ######################################################################## class QCLPage(wiz.WizardPageSimple): + # CLASS IS DEPECRATED + # REPLACED WITH WizardProcessLevelController.py def __init__(self, parent, title, series_service, qcl): """Constructor""" wiz.WizardPageSimple.__init__(self, parent) @@ -62,6 +64,8 @@ def _init_data(self, series): ######################################################################## class VariablePage(wiz.WizardPageSimple): + # CLASS IS DEPECRATED + # REPLACED WITH WizardVariableController.py def __init__(self, parent, title, service_manager, var): """Constructor""" wiz.WizardPageSimple.__init__(self, parent) @@ -108,7 +112,9 @@ def _init_data(self, series_service): ######################################################################## -class MethodPage(wiz.WizardPageSimple): # Raname this page to page method controller +class MethodPage(wiz.WizardPageSimple): + # THIS CLASS IS DEPECRATED + # REPLACED WITH WizardMethodController.py def __init__(self, parent): # pageMethod.pnlMethod.__init__(self, parent) wiz.WizardPageSimple.__init__(self, parent) @@ -211,6 +217,8 @@ def fill_summary(self): ######################################################################## from odmtools.controller.WizardMethodController import WizardMethodController from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController +from odmtools.controller.WizardVariableController import WizardVariableController + class wizSave(wx.wizard.Wizard): def _init_ctrls(self, prnt): @@ -260,11 +268,11 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro = pageIntro.pageIntro(self, "Intro") # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - # self.pgMethod = MethodPage(self) self.pgMethod = WizardMethodController(self) # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) self.pgQCL = WizardProcessLevelController(self) - self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) + # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) + self.pgVariable = WizardVariableController(self) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) self.pgSummary = SummaryPage(self, "Summary", self.series_service) diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py new file mode 100644 index 0000000..31a1d15 --- /dev/null +++ b/odmtools/view/WizardVariableView.py @@ -0,0 +1,68 @@ +import wx + + +class WizardVariableView(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Variable") + static_line = wx.StaticLine(self, size=(-1, 12)) + + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + variable_code_text = wx.StaticText(self, label="Variable Code") + self.variable_code_text_ctrl = wx.TextCtrl(self) + variable_name_text = wx.StaticText(self, label="Variable Name") + self.variable_name_combo = wx.ComboBox(self) + variable_type_text = wx.StaticText(self, label="Variable Type") + self.variable_type_combo = wx.ComboBox(self) + no_data_value_text = wx.StaticText(self, label="No Data Value") + self.no_data_value_text_ctrl = wx.TextCtrl(self, value="-9999") + + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + speciation_text = wx.StaticText(self, label="Speciation") + self.speciation_combo = wx.ComboBox(self) + definition_text = wx.StaticText(self, label="Definition") + self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style Components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + # Add components to sizer + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_code_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_name_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_name_combo, 1, wx.EXPAND | wx.LEFT, 27) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_type_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_type_combo, 1, wx.EXPAND | wx.LEFT, 33) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(no_data_value_text, 0, wx.EXPAND) + row_sizer.Add(self.no_data_value_text_ctrl, 1, wx.EXPAND | wx.LEFT, 29) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(speciation_text, 0, wx.EXPAND) + row_sizer.Add(self.speciation_combo, 1, wx.EXPAND | wx.LEFT, 48) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(definition_text, 0, wx.EXPAND) + row_sizer.Add(self.definition_text_ctrl, 1, wx.EXPAND | wx.LEFT, 52) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) From b49bbe67c876b11650c6f9c25373b573ab81e78d Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 4 Oct 2016 17:32:35 -0600 Subject: [PATCH 021/158] Variable page now has content to select from Removed some warnings, and various improvements and bug fixes --- odmtools/controller/WizardMethodController.py | 8 ++++-- .../controller/WizardVariableController.py | 16 ++++++++++-- odmtools/controller/frmDBConfig.py | 26 ++++++++++++++++++- odmtools/controller/olvDataTable.py | 2 +- odmtools/gui/frmODMTools.py | 5 ---- odmtools/gui/plotProbability.py | 7 ++--- odmtools/gui/wizSave.py | 4 +-- odmtools/view/WizardMethodView.py | 14 +++++++--- odmtools/view/WizardVariableView.py | 9 ++++--- 9 files changed, 66 insertions(+), 25 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 60dee6f..f357a25 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -4,10 +4,14 @@ class WizardMethodController(WizardPageSimple): - def __init__(self, parent): + def __init__(self, parent, series): WizardPageSimple.__init__(self, parent) main_sizer = wx.BoxSizer(wx.VERTICAL) self.method_view = WizardMethodView(self) main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.ALL, 0) - self.SetSizer(main_sizer) \ No newline at end of file + self.SetSizer(main_sizer) + + self.series = series + + self.method_view.method_type_combo.AppendItems(["ABC"]) diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index 8b0579d..1076d4f 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -4,10 +4,22 @@ class WizardVariableController(WizardPageSimple): - def __init__(self, parent): + def __init__(self, parent, service_manager): WizardPageSimple.__init__(self, parent) + self.service_manager = service_manager main_sizer = wx.BoxSizer(wx.VERTICAL) self.variable_view = WizardVariableView(self) main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.ALL, 0) - self.SetSizer(main_sizer) \ No newline at end of file + self.SetSizer(main_sizer) + self.__fetch_data() + + def __fetch_data(self): + cv_service = self.service_manager.get_cv_service() + name_list = [x.term for x in cv_service.get_variable_name_cvs()] + var_unit = [x.name for x in cv_service.get_units_names()] + spec_list = [x.term for x in cv_service.get_speciation_cvs()] + + self.variable_view.variable_name_combo.AppendItems(name_list) + self.variable_view.speciation_combo.AppendItems(spec_list) + self.variable_view.variable_type_combo.AppendItems(var_unit) diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py index 6ef3005..be7e1eb 100644 --- a/odmtools/controller/frmDBConfig.py +++ b/odmtools/controller/frmDBConfig.py @@ -113,8 +113,10 @@ def validateInput(self, conn_dict): # Returns a dictionary of the database values entered in the form def getFieldValues(self): - conn_dict = {} + if self.isFormEmpty(): + return {} + conn_dict = {} conn_dict['engine'] = self.choices[self.cbDatabaseType.GetValue()] conn_dict['user'] = self.txtUser.GetValue() conn_dict['password'] = self.txtPass.GetValue() @@ -123,6 +125,28 @@ def getFieldValues(self): return conn_dict + def isFormEmpty(self): + """ + If any of the entries are empty return true + :return: + """ + if not self.cbDatabaseType.GetValue().strip(): + return True + + if not self.txtUser.GetValue().strip(): + return True + + if not self.txtPass.GetValue().strip(): + return True + + if not self.txtServer.GetValue().strip(): + return True + + if not self.txtDBName.GetValue().strip(): + return True + + return False + def set_field_values(self): conn = self.service_manager.is_valid_connection() if conn is not None: diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 58110d1..975f26c 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -31,7 +31,7 @@ def init(self, memDB): self.SetColumns(columns) self.dataframe = self.memDB.getDataValuesDF() sort_by_index = list(self.dataframe.columns).index("LocalDateTime") - self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) + self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) self.dataObjects = self.dataframe.values.tolist() self.SetObjectGetter(self.ObjectGetter) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index d48f642..a064f2c 100644 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -122,14 +122,9 @@ def _init_s_Items(self, parent): def _init_database(self, quit_if_cancel=True): logger.info("Loading Database...") - - - while True: ## Database connection is valid, therefore proceed through the rest of the program if self.service_manager.is_valid_connection(): - conn_dict = None - series_service = self.createService() conn_dict = self.service_manager.get_current_conn_dict() diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 6e23761..0cb230d 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -133,11 +133,8 @@ def updatePlot(self): self.plots.set_title("\n".join(textwrap.wrap(oneSeries.siteName, 55))) if len(oneSeries.dataTable) > 0: - #self.prob.append( - #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) - #todo FutureWarning: order is deprecated, use sort_values(...) - xValues = oneSeries.Probability.xAxis.order().values - yValues = oneSeries.Probability.yAxis.order().values + xValues = oneSeries.Probability.xAxis.sort_values().values + yValues = oneSeries.Probability.yAxis.sort_values().values ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 957e5ef..fed04da 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -268,11 +268,11 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro = pageIntro.pageIntro(self, "Intro") # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - self.pgMethod = WizardMethodController(self) + self.pgMethod = WizardMethodController(self, self.series_service) # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) self.pgQCL = WizardProcessLevelController(self) # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) - self.pgVariable = WizardVariableController(self) + self.pgVariable = WizardVariableController(self, service_manager=service_manager) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) self.pgSummary = SummaryPage(self, "Summary", self.series_service) diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index 1891c66..3799290 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -1,10 +1,14 @@ import wx +import wx.lib.scrolledpanel -class WizardMethodView(wx.Panel): # Rename this to page method view +class WizardMethodView(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) + # content_panel = wx.lib.scrolledpanel.ScrolledPanel(self) + # content_panel.SetupScrolling() + # Create components header_text = wx.StaticText(self, label="Method") static_line = wx.StaticLine(self, size=(-1, 12)) @@ -15,11 +19,11 @@ def __init__(self, parent): method_name_text = wx.StaticText(self, label="Method Name") self.method_name_text_ctrl = wx.TextCtrl(self) method_type_text = wx.StaticText(self, label="Method Type") - self.method_type_combo = wx.ComboBox(self) + self.method_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) organization_text = wx.StaticText(self, label="Organization") - self.organization_text_ctrl = wx.TextCtrl(self) + self.organization_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) method_link_text = wx.StaticText(self, label="Method Link") self.method_link_text_ctrl = wx.TextCtrl(self) description_text = wx.StaticText(self, label="Description") @@ -28,6 +32,8 @@ def __init__(self, parent): # Style Components font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) header_text.SetFont(font) + self.method_type_combo.SetSelection(0) + self.organization_combo.SetSelection(0) # Add components to sizer row_sizer = wx.BoxSizer(wx.HORIZONTAL) @@ -47,7 +53,7 @@ def __init__(self, parent): row_sizer = wx.BoxSizer(wx.HORIZONTAL) row_sizer.Add(organization_text, 0, wx.EXPAND) - row_sizer.Add(self.organization_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + row_sizer.Add(self.organization_combo, 1, wx.EXPAND | wx.LEFT, 27) optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) row_sizer = wx.BoxSizer(wx.HORIZONTAL) diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py index 31a1d15..1040a20 100644 --- a/odmtools/view/WizardVariableView.py +++ b/odmtools/view/WizardVariableView.py @@ -13,21 +13,24 @@ def __init__(self, parent): variable_code_text = wx.StaticText(self, label="Variable Code") self.variable_code_text_ctrl = wx.TextCtrl(self) variable_name_text = wx.StaticText(self, label="Variable Name") - self.variable_name_combo = wx.ComboBox(self) + self.variable_name_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) variable_type_text = wx.StaticText(self, label="Variable Type") - self.variable_type_combo = wx.ComboBox(self) + self.variable_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) no_data_value_text = wx.StaticText(self, label="No Data Value") self.no_data_value_text_ctrl = wx.TextCtrl(self, value="-9999") optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) speciation_text = wx.StaticText(self, label="Speciation") - self.speciation_combo = wx.ComboBox(self) + self.speciation_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) definition_text = wx.StaticText(self, label="Definition") self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) # Style Components font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) header_text.SetFont(font) + self.variable_name_combo.SetSelection(0) + self.speciation_combo.SetSelection(0) + self.variable_type_combo.SetSelection(0) # Add components to sizer row_sizer = wx.BoxSizer(wx.HORIZONTAL) From d53fc8bb0118ec8e403da792edde82e33337bad1 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 4 Oct 2016 19:08:38 -0600 Subject: [PATCH 022/158] Redesigned the wizard pages to include the old the form plus the new --- odmtools/controller/WizardMethodController.py | 5 +- .../WizardProcessLevelController.py | 7 +- .../controller/WizardVariableController.py | 8 +- odmtools/view/CustomListCtrl.py | 176 ++++++++++++++++++ odmtools/view/WizardMethodView.py | 19 +- odmtools/view/WizardProcessLevelView.py | 13 +- odmtools/view/WizardVariableView.py | 18 +- 7 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 odmtools/view/CustomListCtrl.py diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index f357a25..e135448 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -9,9 +9,10 @@ def __init__(self, parent, series): main_sizer = wx.BoxSizer(wx.VERTICAL) self.method_view = WizardMethodView(self) - main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.ALL, 0) + main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.RIGHT, -16) # Sufficient to hide the scroll bar self.SetSizer(main_sizer) self.series = series - + table_columns = ["Descriptions", "Link", "ID"] + self.method_view.existing_method_table.set_columns(table_columns) self.method_view.method_type_combo.AppendItems(["ABC"]) diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 6555e10..5d7b296 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -9,5 +9,8 @@ def __init__(self, parent): main_sizer = wx.BoxSizer(wx.VERTICAL) self.processing_level_view = WizardProcessLevelView(self) - main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.ALL, 0) - self.SetSizer(main_sizer) \ No newline at end of file + main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.RIGHT, -16) + self.SetSizer(main_sizer) + + table_columns = ["Code", "Definition", "Explanation"] + self.processing_level_view.existing_process_table.set_columns(table_columns) \ No newline at end of file diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index 1076d4f..ae6d834 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -10,8 +10,14 @@ def __init__(self, parent, service_manager): self.service_manager = service_manager main_sizer = wx.BoxSizer(wx.VERTICAL) self.variable_view = WizardVariableView(self) - main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.ALL, 0) + main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.RIGHT, -16) self.SetSizer(main_sizer) + + table_columns = ["Code", "Name", "Speciation", "Units", + "Sample Medium", "Value Type", "IsRegular", "Time Support", + "Time Units", "DataType", "Genaral Category", "NoDataValue"] + self.variable_view.variable_table.set_columns(table_columns) + self.__fetch_data() def __fetch_data(self): diff --git a/odmtools/view/CustomListCtrl.py b/odmtools/view/CustomListCtrl.py new file mode 100644 index 0000000..61fd4dc --- /dev/null +++ b/odmtools/view/CustomListCtrl.py @@ -0,0 +1,176 @@ +import wx +import sys + + +# Place the CustomListCtrl on a panel so it does not grow horizontally +class CustomListCtrl(wx.ListCtrl): + def __init__(self, panel): + wx.ListCtrl.__init__(self, panel, style=wx.LC_REPORT) + self._auto_width_style = wx.LIST_AUTOSIZE + if sys.platform == "win32": + self._auto_width_style = wx.LIST_AUTOSIZE_USEHEADER + + # Message to show in the ListCtrl when it is empty + self.empty_list_message = wx.StaticText(parent=self, label="This list is empty", + style=wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE | wx.FULL_REPAINT_ON_RESIZE) + self.empty_list_message.Hide() + self.empty_list_message.SetForegroundColour(wx.LIGHT_GREY) + self.empty_list_message.SetBackgroundColour(self.GetBackgroundColour()) + self.empty_list_message.SetFont(wx.Font(24, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) + self.Bind(wx.EVT_LIST_COL_CLICK, self.on_list_column_clicked) + + def on_list_column_clicked(self, event): + self.__sort_table_by_column(event.GetColumn()) + + def alternate_row_color(self, color="#DCEBEE"): + for i in range(self.GetItemCount()): + if i % 2 == 0: + self.SetItemBackgroundColour(i, color) + + def auto_size_table(self): + for i in range(self.GetColumnCount()): + self.SetColumnWidth(col=i, width=self._auto_width_style) + + def clear_table(self): + """ + Clears everything in the table including the header names + :return: + """ + self.ClearAll() + + def clear_content(self): + """ + Clears everything in the table except the header names + :return: + """ + self.DeleteAllItems() + + def get_all_selected_rows(self): + """ + Gets all the selected rows + :return: 2D list, list(rows) + """ + index = self.GetFirstSelected() + if index == -1: + return [] # No selected row + + data = [self.get_row_at_index(index)] + for i in range(self.GetSelectedItemCount() - 1): + index = self.GetNextSelected(index) + data.append(self.get_row_at_index(index)) + + return data + + def get_row_at_index(self, index): + """ + Given a row number, return all the data related to the row + :param index: int + :return: list + """ + if index > self.GetItemCount(): + return [] # There are not that many rows in the table + + data = [] + for i in range(self.GetColumnCount()): + data.append(self.GetItem(index, i).GetText()) + return data + + def get_all_data_in_table(self): + data = [] + for i in range(self.GetItemCount()): + data.append(self.get_row_at_index(i)) + return data + + def __sort_table_by_column(self, column_number): + """ + Flip flop between sorting by ascending and descending order. + :param column_number: type(int) + :return: + """ + if column_number < 0: + return + + data = self.get_all_data_in_table() + + column_data = [] + for row in data: + column_data.append(row[column_number]) + + if sorted(column_data) == column_data: + column_data.reverse() + else: + column_data.sort() + + new_data = [] + for i in range(len(column_data)): + for j in range(len(data)): + if column_data[i] in data[j]: + if data[j] not in new_data: + new_data.append(data[j]) + break + + # write the data back + self.clear_content() + self.set_table_content(new_data) + + def get_selected_row(self): + """ + Gets the first selected row + :return: data: type(list). Return None if no row is selected + """ + row_number = self.GetFirstSelected() + if row_number == -1: + return None + + data = [] + for i in range(self.GetColumnCount()): + data.append(self.GetItem(row_number, i).GetText()) + return data + + def remove_selected_row(self): + """ + Only removes the top selected row + :return: + """ + row_number = self.GetFirstSelected() + if row_number == -1: + return # No row is selected + + self.DeleteItem(row_number) + + def set_columns(self, columns): + """ + Sets the name of the columns + :param columns: a list of strings + :return: + """ + self.clear_table() + for i in range(len(columns)): + self.InsertColumn(i, columns[i], width=wx.LIST_AUTOSIZE_USEHEADER) + + def set_empty_message_text(self, text): + self.empty_list_message.SetLabel(text) + + def set_table_content(self, data): + """ + data must be a 2D list [[row1 column1, row1 column2, ...], [row2, column1, row2 column2, ...]] + :param data: 2D list + :return: + """ + + number_of_columns = self.GetColumnCount() + if number_of_columns == 0: + print "No column headers have been created" + return + + # loop through all of the site metadata + for i in range(len(data)): + index = self.InsertStringItem(999999, "") + if number_of_columns < len(data[i]): + raise Exception("The length of the row must match the number of columns") + + for j in range(len(data[i])): + self.SetStringItem(index, j, str(data[i][j])) + + self.auto_size_table() + self.alternate_row_color() \ No newline at end of file diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index 3799290..ee6b25f 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -1,17 +1,19 @@ import wx import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl -class WizardMethodView(wx.Panel): +class WizardMethodView(wx.lib.scrolledpanel.ScrolledPanel): def __init__(self, parent): - wx.Panel.__init__(self, parent) - - # content_panel = wx.lib.scrolledpanel.ScrolledPanel(self) - # content_panel.SetupScrolling() + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) # Create components header_text = wx.StaticText(self, label="Method") static_line = wx.StaticLine(self, size=(-1, 12)) + self.auto_method_radio = wx.RadioButton(self, label="Automatically generate a method") + self.existing_method_radio = wx.RadioButton(self, label="Select an existing method") + self.existing_method_table = CustomListCtrl(self) + self.create_method_radio = wx.RadioButton(self, label="Create a new method") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) method_code_text = wx.StaticText(self, label="Method Code") @@ -34,6 +36,7 @@ def __init__(self, parent): header_text.SetFont(font) self.method_type_combo.SetSelection(0) self.organization_combo.SetSelection(0) + self.SetupScrolling() # Add components to sizer row_sizer = wx.BoxSizer(wx.HORIZONTAL) @@ -69,6 +72,10 @@ def __init__(self, parent): main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.auto_method_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.existing_method_radio, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.existing_method_table, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.create_method_radio, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) - self.SetSizer(main_sizer) \ No newline at end of file + self.SetSizer(main_sizer) diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py index 475d75a..d69a066 100644 --- a/odmtools/view/WizardProcessLevelView.py +++ b/odmtools/view/WizardProcessLevelView.py @@ -1,13 +1,18 @@ import wx +import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl -class WizardProcessLevelView(wx.Panel): +class WizardProcessLevelView(wx.lib.scrolledpanel.ScrolledPanel): def __init__(self, parent): - wx.Panel.__init__(self, parent) + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) # Create components header_text = wx.StaticText(self, label="Processing Level") static_line = wx.StaticLine(self, size=(-1, 12)) + self.existing_process_radio = wx.RadioButton(self, label="Select an existing Processing Level") + self.existing_process_table = CustomListCtrl(self) + self.create_process_level_radio = wx.RadioButton(self, label="Create Processing Level") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) level_code = wx.StaticText(self, label="Processing Level Code") self.level_code_text_ctrl = wx.TextCtrl(self) @@ -20,6 +25,7 @@ def __init__(self, parent): # Style components font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) header_text.SetFont(font) + self.SetupScrolling() # Add components to sizer row_sizer = wx.BoxSizer(wx.HORIZONTAL) @@ -40,6 +46,9 @@ def __init__(self, parent): main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.existing_process_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.existing_process_table, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.create_process_level_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) self.SetSizer(main_sizer) diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py index 1040a20..3b6ec1d 100644 --- a/odmtools/view/WizardVariableView.py +++ b/odmtools/view/WizardVariableView.py @@ -1,13 +1,19 @@ import wx +import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl -class WizardVariableView(wx.Panel): +class WizardVariableView(wx.lib.scrolledpanel.ScrolledPanel): def __init__(self, parent): - wx.Panel.__init__(self, parent) + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) # Create components header_text = wx.StaticText(self, label="Variable") static_line = wx.StaticLine(self, size=(-1, 12)) + table_panel = wx.Panel(self) + self.current_variable_radio = wx.RadioButton(self, label="Use current variable") + self.variable_table = CustomListCtrl(table_panel) + self.existing_variable_radio = wx.RadioButton(self, label="Select an existing variable") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) variable_code_text = wx.StaticText(self, label="Variable Code") @@ -31,8 +37,13 @@ def __init__(self, parent): self.variable_name_combo.SetSelection(0) self.speciation_combo.SetSelection(0) self.variable_type_combo.SetSelection(0) + self.SetupScrolling() # Add components to sizer + table_sizer = wx.BoxSizer() + table_sizer.Add(self.variable_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + row_sizer = wx.BoxSizer(wx.HORIZONTAL) row_sizer.Add(variable_code_text, 0, wx.EXPAND) row_sizer.Add(self.variable_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) @@ -66,6 +77,9 @@ def __init__(self, parent): main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.current_variable_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.existing_variable_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) self.SetSizer(main_sizer) From b220daa512162c81e209f9b83cab62f09ace3429 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 6 Oct 2016 12:46:17 -0600 Subject: [PATCH 023/158] Creating new series in wizard retains the previous data All that is left is to setup the functionality of creating a new series --- ODMTools.py | 5 ---- doc/wxFormBuilder/clsIntro.py | 2 +- odmtools/common/taskServer.py | 2 +- odmtools/controller/WizardMethodController.py | 17 +++++++++-- .../WizardProcessLevelController.py | 23 ++++++++++++-- .../controller/WizardVariableController.py | 30 ++++++++++++++++++- odmtools/controller/frmSeriesSelector.py | 12 ++++---- odmtools/controller/logicEditTools.py | 2 +- odmtools/controller/logicPlotOptions.py | 4 +-- odmtools/controller/olvSeriesSelector.py | 4 +-- odmtools/controller/pageExisting.py | 6 ++-- odmtools/gui/mnuRibbon.py | 8 ++--- odmtools/gui/wizSave.py | 8 ++--- odmtools/odmdata/memory_database.py | 4 +-- odmtools/odmdata/series.py | 2 +- odmtools/odmservices/cv_service.py | 1 + odmtools/odmservices/edit_service.py | 28 ++++++++--------- odmtools/odmservices/series_service.py | 10 +++---- odmtools/view/WizardMethodView.py | 9 ++++-- odmtools/view/WizardProcessLevelView.py | 9 ++++-- odmtools/view/clsIntro.py | 4 +-- tests/test_gui/test_plotProbability.py | 2 +- tests/test_gui/test_wizSave.py | 2 +- tests/test_odmdata/test_pandas_memory_db.py | 2 +- tests/test_odmservices/test_edit_service.py | 4 +-- tests/test_odmservices/test_series_service.py | 18 +++++------ tests/test_util.py | 2 +- 27 files changed, 142 insertions(+), 78 deletions(-) diff --git a/ODMTools.py b/ODMTools.py index ecbea16..7552dbe 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -60,11 +60,6 @@ def OnInit(self): app= self.frame return True -def runODM(): - app = wx.App(False) - #frame = create(None) - #frame.Show() - app.MainLoop() if __name__ == '__main__': logger.info("Welcome to ODMTools Python. Please wait as system loads") diff --git a/doc/wxFormBuilder/clsIntro.py b/doc/wxFormBuilder/clsIntro.py index dd3d888..094fe78 100644 --- a/doc/wxFormBuilder/clsIntro.py +++ b/doc/wxFormBuilder/clsIntro.py @@ -29,7 +29,7 @@ def __init__( self, parent ): bSizer2 = wx.BoxSizer( wx.VERTICAL ) - self.lblHow = wx.StaticText( self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lblHow = wx.StaticText( self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", wx.DefaultPosition, wx.DefaultSize, 0 ) self.lblHow.Wrap( -1 ) bSizer2.Add( self.lblHow, 0, wx.ALL, 15 ) diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index 0620112..85b949e 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -131,7 +131,7 @@ def worker(cls, dispatcher): if task_type == "InitEditValues": connection = SeriesService("sqlite:///:memory:") df = task[1] - logger.debug("Load series from db") + logger.debug("Load series_service from db") df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) logger.debug("done loading database") result = connection diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index e135448..1d22a49 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -4,7 +4,7 @@ class WizardMethodController(WizardPageSimple): - def __init__(self, parent, series): + def __init__(self, parent, series_service): WizardPageSimple.__init__(self, parent) main_sizer = wx.BoxSizer(wx.VERTICAL) @@ -12,7 +12,20 @@ def __init__(self, parent, series): main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.RIGHT, -16) # Sufficient to hide the scroll bar self.SetSizer(main_sizer) - self.series = series + self.series_service = series_service table_columns = ["Descriptions", "Link", "ID"] self.method_view.existing_method_table.set_columns(table_columns) self.method_view.method_type_combo.AppendItems(["ABC"]) + self.__fetch_data() + + def __fetch_data(self): + methods = self.series_service.get_all_methods() + data = [] + for meth in methods: + data.append([ + meth.description, + meth.link, + meth.id + ]) + + self.method_view.existing_method_table.set_table_content(data=data) diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 5d7b296..87dd78b 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -4,13 +4,30 @@ class WizardProcessLevelController(WizardPageSimple): - def __init__(self, parent): + def __init__(self, parent, service_manager): WizardPageSimple.__init__(self, parent) + self.service_manager = service_manager main_sizer = wx.BoxSizer(wx.VERTICAL) self.processing_level_view = WizardProcessLevelView(self) main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.RIGHT, -16) self.SetSizer(main_sizer) - table_columns = ["Code", "Definition", "Explanation"] - self.processing_level_view.existing_process_table.set_columns(table_columns) \ No newline at end of file + table_columns = ["Code", "Definition", "Explanation", "ID"] + self.processing_level_view.existing_process_table.set_columns(table_columns) + self.__fetch_data() + + def __fetch_data(self): + series_service = self.service_manager.get_series_service() + processes = series_service.get_all_qcls() + + data = [] + for proc in processes: + data.append([ + proc.code, + proc.definition, + proc.explanation, + proc.id + ]) + + self.processing_level_view.existing_process_table.set_table_content(data=data) diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index ae6d834..eadc178 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -15,12 +15,14 @@ def __init__(self, parent, service_manager): table_columns = ["Code", "Name", "Speciation", "Units", "Sample Medium", "Value Type", "IsRegular", "Time Support", - "Time Units", "DataType", "Genaral Category", "NoDataValue"] + "Time Units", "DataType", "Genaral Category", "NoDataValue", "ID"] self.variable_view.variable_table.set_columns(table_columns) self.__fetch_data() def __fetch_data(self): + self.__populate_variable_table() + cv_service = self.service_manager.get_cv_service() name_list = [x.term for x in cv_service.get_variable_name_cvs()] var_unit = [x.name for x in cv_service.get_units_names()] @@ -29,3 +31,29 @@ def __fetch_data(self): self.variable_view.variable_name_combo.AppendItems(name_list) self.variable_view.speciation_combo.AppendItems(spec_list) self.variable_view.variable_type_combo.AppendItems(var_unit) + + def __populate_variable_table(self): + series_serivce = self.service_manager.get_series_service() + variables = series_serivce.get_all_variables() + data = [] + for var in variables: + data.append([var.code, + var.name, + var.speciation, + var.variable_unit.name, + var.sample_medium, + var.value_type, + var.is_regular, + var.time_support, + var.time_unit.name, + var.data_type, + var.general_category, + var.no_data_value, + var.id]) + + self.variable_view.variable_table.set_table_content(data=data) + + + + + diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index c06324e..b7d7327 100644 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -61,7 +61,7 @@ def resetDB(self, series_service): self.Layout() def initTableSeries(self): - """Set up columns and objects to be used in the objectlistview to be visible in the series selector + """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector :return: """ @@ -80,7 +80,7 @@ def initTableSeries(self): #self.tblSeries.SaveObject(object) def refreshTableSeries(self, db): - """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' + """ Refreshes the objectlistview to include newly saved database series_service and preserve which series_service was 'checked' for plotting/editing :return: @@ -474,7 +474,7 @@ def setFilter(self, site_code='', var_code='', advfilter=''): def onReadyToPlot(self, event): - """Plots a series selected from the series selector + """Plots a series_service selected from the series_service selector :param event: EVT_OVL_CHECK_EVENT type """ @@ -529,7 +529,7 @@ def getSelectedObject(self, event): def onReadyToEdit(self): - """Choose a series to edit from the series selector + """Choose a series_service to edit from the series_service selector :return: """ @@ -565,8 +565,8 @@ def onReadyToEdit(self): return True, object.id#, self.memDB else: isSelected = False - logger.debug("series was not checked") - val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", + logger.debug("series_service was not checked") + val_2 = wx.MessageBox("Visualization is limited to 6 series_service.", "Can't add plot", wx.OK | wx.ICON_INFORMATION) diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index b88e35c..2504bd8 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -19,7 +19,7 @@ def __init__(self, parent, script, edit_service, connection_string, record=Fals self._record = record self._serv_man = parent - self._edit_error = "no series selected for editing" + self._edit_error = "no series_service selected for editing" self._add_point_req_error = "A required field was left empty" self._add_point_format_error = "A date is not formatted correctly" diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 25f69b6..e074eb1 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -293,7 +293,7 @@ def getSeriesInfo(self, seriesID): series = self.getSeriesById(seriesID) if not series: - message = "Please check your database connection. Unable to retrieve series %d from the database" % seriesID + message = "Please check your database connection. Unable to retrieve series_service %d from the database" % seriesID wx.MessageBox(message, 'ODMTool Python', wx.OK | wx.ICON_EXCLAMATION) return @@ -339,7 +339,7 @@ def updateDateRange(self, startDate=None, endDate=None): self.currentStart = startDate self.currentEnd = endDate else: - #this returns the series to its full daterange + #this returns the series_service to its full daterange data = self.memDB.getDataValuesforGraph(key, seriesInfo.noDataValue, seriesInfo.startDate, seriesInfo.endDate) self.isSubsetted = False diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index f49fdb0..b3f510d 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -52,7 +52,7 @@ def _buildColumns(self): for key, value in series.returnDict().iteritems()] self.SetColumns(seriesColumns) - """User can select series using the mouse to click on check boxes """ + """User can select series_service using the mouse to click on check boxes """ def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): """ @@ -80,7 +80,7 @@ def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): self.RefreshIndex(rowIndex, modelObject) else: - wx.MessageBox("Visualization is limited to {0} series.".format(self.allowedLimit), "Can't add plot", + wx.MessageBox("Visualization is limited to {0} series_service.".format(self.allowedLimit), "Can't add plot", wx.OK | wx.ICON_INFORMATION) else: if checkedlen > 0: diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 0544cfe..4d92291 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -20,7 +20,7 @@ def __init__(self, parent, title, series_service , site): sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = sizer self.SetSizer(sizer) - #self.series = series + #self.series_service = series_service title = wx.StaticText(self, -1, title) title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) @@ -28,7 +28,7 @@ def __init__(self, parent, title, series_service , site): sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) self.pnlExisting = clsExisting.pnlExisting(self) #, id=wxID_PNLEXISTING, name=u'pnlExisting', #pos=wx.Point(536, 285), size=wx.Size(439, 357), - #style=wx.TAB_TRAVERSAL)#, sm = service_man, series = series) + #style=wx.TAB_TRAVERSAL)#, sm = service_man, series_service = series_service) self.sizer.Add(self.pnlExisting, 85, wx.ALL, 5) self._init_data(series_service, site.id) @@ -70,7 +70,7 @@ def getSeries(self): return selectedObject.method, selectedObject.quality_control_level, selectedObject.variable def initTable(self, dbservice, site_id): - """Set up columns and objects to be used in the objectlistview to be visible in the series selector""" + """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector""" seriesColumns = [clsExisting.ColumnDefn(key, align="left", minimumWidth=-1, valueGetter=value, diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index 360f6cc..7c4f2f9 100644 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -514,7 +514,7 @@ def onEditSeries(self, event=None): Publisher.sendMessage(("stopEdit"), event=event) #self.parent.stopEdit() - # # Start editing right when a series is being edited + # # Start editing right when a series_service is being edited #self.parent.getRecordService().toggle_record() #record_service.toggle_record() @@ -608,7 +608,7 @@ def onPlotSelection(self, event): value = 3 elif event.Id == wxID_RIBBONPLOTSUMMARY: value = 4 - # TODO fix case where the plot enables the buttons without checking if series is actually selected + # TODO fix case where the plot enables the buttons without checking if series_service is actually selected self.enableButtons(value, True) Publisher.sendMessage(("select.Plot"), value=value) event.Skip() @@ -650,7 +650,7 @@ def enableButtons(self, plot, isActive): self.spnBins.Enabled = False self.enableDateSelection(False) - ##tims series or probability + ##tims series_service or probability elif plot == 0 or plot == 1: self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSTYPE, True) self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSLEGEND, True) @@ -686,7 +686,7 @@ def enableButtons(self, plot, isActive): # TODO change states when points are selected rather than all at once def toggleEditButtons(self, state): - # edit when series is selected for editing + # edit when series_service is selected for editing self.main_bar.EnableButton(wxID_RIBBONEDITRESTORE, state) self.main_bar.EnableButton(wxID_RIBBONEDITSAVE, state) self.edit_bar.EnableButton(wxID_RIBBONEDITFILTER, state) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index fed04da..c0dc75d 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -145,8 +145,8 @@ def __init__(self, parent): # # self._init_data(self.panel.series_service) # -# def _init_data(self, series): -# meth = series.get_all_methods() +# def _init_data(self, series_service): +# meth = series_service.get_all_methods() # index = 0 # for m, i in zip(meth, range(len(meth))): # num_items = self.panel.lstMethods.GetItemCount() @@ -270,7 +270,7 @@ def __init__(self, parent, service_manager, record_service): # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) self.pgMethod = WizardMethodController(self, self.series_service) # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) - self.pgQCL = WizardProcessLevelController(self) + self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager) # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) self.pgVariable = WizardVariableController(self, service_manager=service_manager) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) @@ -363,7 +363,7 @@ def on_wizard_finished(self, event): if self.pgExisting.pnlExisting.rbAppend.GetValue(): keyword = "append to" - message = "You are about to " + keyword + " an existing series,\nthis action cannot be undone.\nWould you like to continue?\n" + message = "You are about to " + keyword + " an existing series_service,\nthis action cannot be undone.\nWould you like to continue?\n" cont = wx.MessageBox(message, 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) if cont == 2: closeSuccessful = True diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 2ae1763..a638527 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -200,7 +200,7 @@ def initEditValues(self, seriesID): :return: nothing """ if not self.editLoaded: - logger.debug("Load series from db") + logger.debug("Load series_service from db") self.df = self.series_service.get_values_by_series(seriesID) self.editLoaded = True @@ -218,7 +218,7 @@ def initEditValues(self, seriesID): index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") else: - logger.debug("no data in series") + logger.debug("no data in series_service") def changeSeriesIDs(self, var=None, qcl=None, method=None): """ diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py index 7688555..a287d85 100644 --- a/odmtools/odmdata/series.py +++ b/odmtools/odmdata/series.py @@ -85,7 +85,7 @@ class Series(Base): "DataValue.quality_control_level_id == Series.quality_control_level_id)", foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", order_by="DataValue.local_date_time", - backref="series") + backref="series_service") site = relationship(Site) variable = relationship(Variable) diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py index 3466d0c..4b448d2 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/cv_service.py @@ -54,6 +54,7 @@ def get_offset_type_cvs(self): def get_speciation_cvs(self): result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() return result + # return self.read_service.getCVs(type="Speciation") # Returns Error running Query, def get_sample_medium_cvs(self): result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 315e927..567b0dc 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -253,7 +253,7 @@ def get_series(self): return self.memDB.series_service.get_series_by_id(self._series_id) def get_series_points(self): - # all point in the series + # all point in the series_service return self._series_points def get_series_points_df(self): @@ -280,7 +280,7 @@ def get_filtered_dates(self): return self.filtered_dataframe def get_filter_list(self): - # true or false list the length of the entire series. true indicate the point is selected + # true or false list the length of the entire series_service. true indicate the point is selected return self._filter_list def get_qcl(self, qcl_id): @@ -442,7 +442,7 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove - #if is new series remove valueids + #if is new series_service remove valueids #if is_new_series: dvs["ValueID"] = None ''' @@ -451,7 +451,7 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove ''' series = self.memDB.series_service.get_series_by_id(self._series_id) - logger.debug("original editing series id: %s" % str(series.id)) + logger.debug("original editing series_service id: %s" % str(series.id)) if (var or method or qcl ): tseries = self.memDB.series_service.get_series_by_id_quint(site_id=int(series.site_id), @@ -462,7 +462,7 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove qcl_id=qcl_id if qcl else int( series.quality_control_level_id)) if tseries: - logger.debug("Save existing series ID: %s" % str(tseries.id)) + logger.debug("Save existing series_service ID: %s" % str(tseries.id)) series = tseries else: print "Series doesn't exist (if you are not, you should be running SaveAs)" @@ -512,11 +512,11 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove #pass self.memDB.series_service.delete_values_by_series(series) elif append: - #if series end date is after dvs startdate + #if series_service end date is after dvs startdate dbend = series.end_date_time dfstart = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form) overlap = dbend>= dfstart - #leave series start dates to those previously set + #leave series_service start dates to those previously set series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form) series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #TODO figure out how to calculate the new value count @@ -532,7 +532,7 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove - #logger.debug("series.data_values: %s" % ([x for x in series.data_values])) + #logger.debug("series_service.data_values: %s" % ([x for x in series_service.data_values])) dvs.drop('ValueID', axis=1, inplace=True) return series, dvs @@ -546,7 +546,7 @@ def save(self): series, dvs = self.updateSeries(is_new_series=False) if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") + logger.debug("series_service saved!") return True else: logger.debug("The Save was unsuccessful") @@ -562,7 +562,7 @@ def save_as(self, var=None, method=None, qcl=None): series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) if self.memDB.series_service.save_new_series(series, dvs): - logger.debug("series saved!") + logger.debug("series_service saved!") return True else: logger.debug("The Save As Function was Unsuccessful") @@ -572,7 +572,7 @@ def save_appending(self, var= None, method = None, qcl=None, overwrite=False): series, dvs = self.updateSeries(var, method, qcl, is_new_series=False, append= True, overwrite=overwrite) if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") + logger.debug("series_service saved!") return True else: logger.debug("The Append Existing Function was Unsuccessful") @@ -587,7 +587,7 @@ def save_existing(self, var=None, method=None, qcl=None): """ series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") + logger.debug("series_service saved!") return True else: logger.debug("The Save As Existing Function was Unsuccessful") @@ -610,8 +610,8 @@ def create_variable(self, code, name, speciation, variable_unit_id, sample_mediu general_category, no_data_value) def reconcile_dates(self, parent_series_id): - # FUTURE FEATURE: pull in new field data from another series and add to this series - # (i.e one series contains new field data of an edited series at a higher qcl) + # FUTURE FEATURE: pull in new field data from another series_service and add to this series_service + # (i.e one series_service contains new field data of an edited series_service at a higher qcl) pass diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index c27158b..834014d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -274,7 +274,7 @@ def get_samples_by_series_id(self, series_id): # Series Catalog methods def get_all_series(self): """ - Returns all series as a modelObject + Returns all series_service as a modelObject :return: List[Series] """ @@ -528,7 +528,7 @@ def save_new_series(self, series, dvs): self._edit_session.rollback() raise e - logger.info("A new series was added to the database, series id: "+str(series.id)) + logger.info("A new series_service was added to the database, series_service id: "+str(series.id)) return True def save_values(self, values): @@ -541,7 +541,7 @@ def save_values(self, values): def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): """ - series -> Result in ODM2 + series_service -> Result in ODM2 :param data_values: :param site_id: :param variable_id: @@ -671,7 +671,7 @@ def delete_series(self, series): self._edit_session.delete(delete_series) self._edit_session.commit() except Exception as e: - message = "series was not successfully deleted: %s" % e + message = "series_service was not successfully deleted: %s" % e print message logger.error(message) raise e @@ -690,7 +690,7 @@ def delete_values_by_series(self, series, startdate = None): source_id = series.source_id, quality_control_level_id = series.quality_control_level_id) if startdate is not None: - #start date indicates what day you should start deleting values. the values will delete to the end of the series + #start date indicates what day you should start deleting values. the values will delete to the end of the series_service return q.filter(DataValue.local_date_time >= startdate).delete() else: return q.delete() diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index ee6b25f..532aac1 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -12,7 +12,8 @@ def __init__(self, parent): static_line = wx.StaticLine(self, size=(-1, 12)) self.auto_method_radio = wx.RadioButton(self, label="Automatically generate a method") self.existing_method_radio = wx.RadioButton(self, label="Select an existing method") - self.existing_method_table = CustomListCtrl(self) + table_panel = wx.Panel(self) + self.existing_method_table = CustomListCtrl(table_panel) self.create_method_radio = wx.RadioButton(self, label="Create a new method") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) @@ -39,6 +40,10 @@ def __init__(self, parent): self.SetupScrolling() # Add components to sizer + table_sizer = wx.BoxSizer() + table_sizer.Add(self.existing_method_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + row_sizer = wx.BoxSizer(wx.HORIZONTAL) row_sizer.Add(method_code_text, 0, wx.EXPAND) row_sizer.Add(self.method_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 24) @@ -74,7 +79,7 @@ def __init__(self, parent): main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(self.auto_method_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(self.existing_method_radio, 0, wx.EXPAND | wx.TOP, 5) - main_sizer.Add(self.existing_method_table, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(self.create_method_radio, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py index d69a066..b88dec6 100644 --- a/odmtools/view/WizardProcessLevelView.py +++ b/odmtools/view/WizardProcessLevelView.py @@ -11,7 +11,8 @@ def __init__(self, parent): header_text = wx.StaticText(self, label="Processing Level") static_line = wx.StaticLine(self, size=(-1, 12)) self.existing_process_radio = wx.RadioButton(self, label="Select an existing Processing Level") - self.existing_process_table = CustomListCtrl(self) + table_panel = wx.Panel(self) + self.existing_process_table = CustomListCtrl(table_panel) self.create_process_level_radio = wx.RadioButton(self, label="Create Processing Level") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) level_code = wx.StaticText(self, label="Processing Level Code") @@ -28,6 +29,10 @@ def __init__(self, parent): self.SetupScrolling() # Add components to sizer + table_sizer = wx.BoxSizer(wx.VERTICAL) + table_sizer.Add(self.existing_process_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + row_sizer = wx.BoxSizer(wx.HORIZONTAL) row_sizer.Add(level_code, 0, wx.EXPAND) row_sizer.Add(self.level_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) @@ -47,7 +52,7 @@ def __init__(self, parent): main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(self.existing_process_radio, 0, wx.EXPAND | wx.TOP, 10) - main_sizer.Add(self.existing_process_table, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(self.create_process_level_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) diff --git a/odmtools/view/clsIntro.py b/odmtools/view/clsIntro.py index 8d9b338..2239e3b 100644 --- a/odmtools/view/clsIntro.py +++ b/odmtools/view/clsIntro.py @@ -34,7 +34,7 @@ # # bSizer2 = wx.BoxSizer(wx.VERTICAL) # -# self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", +# self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", # wx.DefaultPosition, wx.DefaultSize, 0) # self.lblHow.Wrap(-1) # bSizer2.Add(self.lblHow, 0, wx.ALL, 15) @@ -139,7 +139,7 @@ def __init__(self, parent): bSizer2 = wx.BoxSizer(wx.VERTICAL) - self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", + self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", wx.DefaultPosition, wx.DefaultSize, 0) self.lblHow.Wrap(-1) bSizer2.Add(self.lblHow, 0, wx.ALL, 15) diff --git a/tests/test_gui/test_plotProbability.py b/tests/test_gui/test_plotProbability.py index ffac10b..3fc9eac 100644 --- a/tests/test_gui/test_plotProbability.py +++ b/tests/test_gui/test_plotProbability.py @@ -33,7 +33,7 @@ def setup(self): self.series = add_series(self.session) print "Series: ", self.series self.memory_db.initEditValues(self.series.id) - # add_bulk_data_values(self.session, self.series) + # add_bulk_data_values(self.session, self.series_service) def test_onPlotType(self): diff --git a/tests/test_gui/test_wizSave.py b/tests/test_gui/test_wizSave.py index 038faee..0685d6b 100644 --- a/tests/test_gui/test_wizSave.py +++ b/tests/test_gui/test_wizSave.py @@ -32,7 +32,7 @@ def setup(self): self.memory_database = MemoryDatabase() self.memory_database.set_series_service(self.series_service) - # self.memory_database.initEditValues(self.series.id) + # self.memory_database.initEditValues(self.series_service.id) self.app = wx.App() self.frame = wx.Frame(None) diff --git a/tests/test_odmdata/test_pandas_memory_db.py b/tests/test_odmdata/test_pandas_memory_db.py index 94ef7ee..d4c58cc 100644 --- a/tests/test_odmdata/test_pandas_memory_db.py +++ b/tests/test_odmdata/test_pandas_memory_db.py @@ -10,7 +10,7 @@ class TestPandasMemoryDB: """ - Test to Load up a series from a dataframe and load it into an in memory database + Test to Load up a series_service from a dataframe and load it into an in memory database """ def setup(self): self.connection_string = "sqlite:///:memory:" diff --git a/tests/test_odmservices/test_edit_service.py b/tests/test_odmservices/test_edit_service.py index ebe93f7..d2627e8 100644 --- a/tests/test_odmservices/test_edit_service.py +++ b/tests/test_odmservices/test_edit_service.py @@ -18,7 +18,7 @@ def setup(self): self.session = self.memory_database.series_service._session_factory.getSession() self.series = test_util.add_series_bulk_data(self.session) - #assert len(self.series.data_values) == 100 + #assert len(self.series_service.data_values) == 100 self.edit_service =EditService(1, connection= self.memory_database) @@ -90,7 +90,7 @@ def test_save_append_keep(self): #TODO add custon test len1= len(self.series.data_values) - # keep data from original series if overlap: + # keep data from original series_service if overlap: svalue = self.series.data_values[0] diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 1b4eafa..c73dfc0 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -209,18 +209,18 @@ def test_series_exists(self): ''' def test_save_series(self): - series = Series() + series_service = Series() site = test_util.add_site(self.session) variable = test_util.add_variable(self.session) method = test_util.add_method(self.session) source = test_util.add_source(self.session) qcl = test_util.add_qcl(self.session) - series.site_id = site.id - series.variable_id = variable.id - series.method_id = method.id - series.source_id = source.id - series.quality_control_level_id = qcl.id + series_service.site_id = site.id + series_service.variable_id = variable.id + series_service.method_id = method.id + series_service.source_id = source.id + series_service.quality_control_level_id = qcl.id dvs = [] for val in range(10): @@ -233,10 +233,10 @@ def test_save_series(self): dv.quality_control_level_id = qcl.id dvs.append(dv) - print series.variable_code - assert self.series_service.save_series(series) + print series_service.variable_code + assert self.series_service.save_series(series_service) assert self.series_service.series_exists(site.id, variable.id, method.id, source.id, qcl.id) - assert not self.series_service.save_series(series) + assert not self.series_service.save_series(series_service) ''' def test_get_data_value_by_id(self): diff --git a/tests/test_util.py b/tests/test_util.py index 43789d3..444fb7a 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -13,7 +13,7 @@ def build_db(engine): def add_bulk_data_values(session, series, dvs_size): """ - Load up exampleData.csv into a series' datavalues field + Load up exampleData.csv into a series_service' datavalues field """ assert 10000 >= dvs_size > 0 path = os.path.dirname(os.path.realpath(__file__)) From 190433eb5fabfa567a0e5239da9893e1b8a448dd Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 10 Oct 2016 14:14:25 -0600 Subject: [PATCH 024/158] Fixed some bugs when creating a new series Chooinsg an existing and new method/variable/pcl will active its respective panels, also added some methods to finish creating a new series such as the getMethods, getVariales, etc --- odmtools/controller/WizardMethodController.py | 49 +++++++++++++++++++ .../WizardProcessLevelController.py | 34 +++++++++++++ .../controller/WizardVariableController.py | 46 ++++++++++++++++- odmtools/gui/wizSave.py | 10 ++-- odmtools/view/CustomListCtrl.py | 1 + odmtools/view/WizardMethodView.py | 3 +- odmtools/view/WizardVariableView.py | 4 +- 7 files changed, 139 insertions(+), 8 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 1d22a49..e0c245d 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -1,6 +1,7 @@ import wx from wx.wizard import WizardPageSimple from odmtools.view.WizardMethodView import WizardMethodView +from odmtools.odmdata import Method class WizardMethodController(WizardPageSimple): @@ -16,8 +17,37 @@ def __init__(self, parent, series_service): table_columns = ["Descriptions", "Link", "ID"] self.method_view.existing_method_table.set_columns(table_columns) self.method_view.method_type_combo.AppendItems(["ABC"]) + self.on_auto_radio(None) + + self.method_view.auto_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_auto_radio) + self.method_view.existing_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_method_radio) + self.method_view.create_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_method_radio) + self.__fetch_data() + def on_auto_radio(self, event): + self.method_view.existing_method_table.Enable(False) + self.__set_create_method_section_(False) + + def on_existing_method_radio(self, event): + self.method_view.existing_method_table.Enable() + self.__set_create_method_section_(False) + + def __set_create_method_section_(self, active): + if not isinstance(active, bool): + raise Exception("active must be type bool") + + self.method_view.method_code_text_ctrl.Enable(active) + self.method_view.method_name_text_ctrl.Enable(active) + self.method_view.method_type_combo.Enable(active) + self.method_view.organization_combo.Enable(active) + self.method_view.method_link_text_ctrl.Enable(active) + self.method_view.description_text_ctrl.Enable(active) + + def on_create_method_radio(self, event): + self.method_view.existing_method_table.Disable() + self.__set_create_method_section_(True) + def __fetch_data(self): methods = self.series_service.get_all_methods() data = [] @@ -29,3 +59,22 @@ def __fetch_data(self): ]) self.method_view.existing_method_table.set_table_content(data=data) + + def getMethod(self): + m = Method() + if self.method_view.auto_method_radio.GetValue(): + description = "Values derived from ODM Tools Python" + m = self.series_service.get_method_by_description(description) + if m is None: + m = Method() + m.description = description + elif self.method_view.existing_method_radio.GetValue(): + index = self.method_view.existing_method_table.GetFirstSelected() + desc = self.method_view.existing_method_table.GetItem(index, 0).GetText() + + m = self.series_service.get_method_by_description(desc) + elif self.method_view.create_method_radio.GetValue(): + m.description = self.method_view.description_text_ctrl.GetValue() + + + return m diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 87dd78b..76f3aed 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -1,6 +1,7 @@ import wx from odmtools.view.WizardProcessLevelView import WizardProcessLevelView from wx.wizard import WizardPageSimple +from odmtools.odmdata import QualityControlLevel class WizardProcessLevelController(WizardPageSimple): @@ -17,6 +18,25 @@ def __init__(self, parent, service_manager): self.processing_level_view.existing_process_table.set_columns(table_columns) self.__fetch_data() + self.processing_level_view.create_process_level_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) + self.processing_level_view.existing_process_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) + + def on_create_radio(self, event): + self.processing_level_view.existing_process_table.Enable(False) + self.__set_create_proces_section(True) + + def on_existing_radio(self, event): + self.processing_level_view.existing_process_table.Enable(True) + self.__set_create_proces_section(False) + + def __set_create_proces_section(self, active): + if not isinstance(active, bool): + raise Exception("activet must be type bool") + + self.processing_level_view.level_code_text_ctrl.Enable(active) + self.processing_level_view.definition_text_ctrl.Enable(active) + self.processing_level_view.explanation_text_ctrl.Enable(active) + def __fetch_data(self): series_service = self.service_manager.get_series_service() processes = series_service.get_all_qcls() @@ -31,3 +51,17 @@ def __fetch_data(self): ]) self.processing_level_view.existing_process_table.set_table_content(data=data) + + def getQCL(self): + q = QualityControlLevel() + if self.processing_level_view.create_process_level_radio.GetValue(): + q.code = self.processing_level_view.level_code_text_ctrl.GetValue() + q.definition = self.processing_level_view.definition_text_ctrl.GetValue() + q.explanation = self.processing_level_view.explanation_text_ctrl.GetValue() + + elif self.processing_level_view.existing_process_radio.GetValue(): + selected_row = self.processing_level_view.existing_process_table.get_selected_row() + code = selected_row[0] + q = self.service_manager.get_series_service().get_qcl_by_code(qcl_code=code) + + return q diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index eadc178..f15f345 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -1,13 +1,15 @@ import wx from odmtools.view.WizardVariableView import WizardVariableView from wx.wizard import WizardPageSimple +from odmtools.odmdata import Variable class WizardVariableController(WizardPageSimple): - def __init__(self, parent, service_manager): + def __init__(self, parent, service_manager, current_variable): WizardPageSimple.__init__(self, parent) self.service_manager = service_manager + self.current_variable = current_variable main_sizer = wx.BoxSizer(wx.VERTICAL) self.variable_view = WizardVariableView(self) main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.RIGHT, -16) @@ -17,8 +19,35 @@ def __init__(self, parent, service_manager): "Sample Medium", "Value Type", "IsRegular", "Time Support", "Time Units", "DataType", "Genaral Category", "NoDataValue", "ID"] self.variable_view.variable_table.set_columns(table_columns) + self.on_current_radio(None) self.__fetch_data() + self.variable_view.current_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_current_radio) + self.variable_view.existing_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) + self.variable_view.create_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) + + def on_current_radio(self, event): + self.variable_view.variable_table.Enable(False) + self.__set_create_variable_section(False) + + def on_create_radio(self, event): + self.variable_view.variable_table.Enable(False) + self.__set_create_variable_section(True) + + def on_existing_radio(self, event): + self.variable_view.variable_table.Enable(True) + self.__set_create_variable_section(False) + + def __set_create_variable_section(self, active): + if not isinstance(active, bool): + raise Exception("active must be type bool") + + self.variable_view.variable_code_text_ctrl.Enable(active) + self.variable_view.variable_name_combo.Enable(active) + self.variable_view.variable_type_combo.Enable(active) + self.variable_view.no_data_value_text_ctrl.Enable(active) + self.variable_view.speciation_combo.Enable(active) + self.variable_view.definition_text_ctrl.Enable(active) def __fetch_data(self): self.__populate_variable_table() @@ -53,6 +82,21 @@ def __populate_variable_table(self): self.variable_view.variable_table.set_table_content(data=data) + def get_variable(self): + v = Variable() + if self.variable_view.current_variable_radio.GetValue(): + v = self.current_variable + elif self.variable_view.existing_variable_radio.GetValue(): + row = self.variable_view.variable_table.get_selected_row() + code = row[0] + v = self.service_manager.get_series_service().get_variable_by_code(code) + + elif self.variable_view.create_variable_radio.GetValue(): + # v = self.createdVar + pass + + return v + diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index c0dc75d..a8837d9 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -238,9 +238,9 @@ def get_metadata(self): if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): logger.debug("SaveAs") - method = self.pgMethod.panel.getMethod() - qcl = self.pgQCL.panel.getQCL() - variable = self.pgVariable.panel.getVariable() + method = self.pgMethod.getMethod() + qcl = self.pgQCL.getQCL() + variable = self.pgVariable.get_variable() elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): logger.debug("Save") method = self.currSeries.method @@ -272,7 +272,8 @@ def __init__(self, parent, service_manager, record_service): # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager) # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) - self.pgVariable = WizardVariableController(self, service_manager=service_manager) + self.pgVariable = WizardVariableController(self, service_manager=service_manager, + current_variable=self.currSeries.variable) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) self.pgSummary = SummaryPage(self, "Summary", self.series_service) @@ -302,7 +303,6 @@ def __init__(self, parent, service_manager, record_service): self.Destroy() def on_page_changed(self, event): - #if isinstance(event.Page, pageSummary.pnlSummary): if event.Page == self.pgSummary: self.pgSummary.fill_summary() diff --git a/odmtools/view/CustomListCtrl.py b/odmtools/view/CustomListCtrl.py index 61fd4dc..4dc2e9b 100644 --- a/odmtools/view/CustomListCtrl.py +++ b/odmtools/view/CustomListCtrl.py @@ -172,5 +172,6 @@ def set_table_content(self, data): for j in range(len(data[i])): self.SetStringItem(index, j, str(data[i][j])) + self.Select(0) self.auto_size_table() self.alternate_row_color() \ No newline at end of file diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index 532aac1..612e232 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -38,6 +38,7 @@ def __init__(self, parent): self.method_type_combo.SetSelection(0) self.organization_combo.SetSelection(0) self.SetupScrolling() + self.auto_method_radio.SetValue(True) # Add components to sizer table_sizer = wx.BoxSizer() @@ -80,7 +81,7 @@ def __init__(self, parent): main_sizer.Add(self.auto_method_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(self.existing_method_radio, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 5) - main_sizer.Add(self.create_method_radio, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.create_method_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) self.SetSizer(main_sizer) diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py index 3b6ec1d..df9a8bf 100644 --- a/odmtools/view/WizardVariableView.py +++ b/odmtools/view/WizardVariableView.py @@ -14,7 +14,7 @@ def __init__(self, parent): self.current_variable_radio = wx.RadioButton(self, label="Use current variable") self.variable_table = CustomListCtrl(table_panel) self.existing_variable_radio = wx.RadioButton(self, label="Select an existing variable") - + self.create_variable_radio = wx.RadioButton(self, label="Create new variable") required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) variable_code_text = wx.StaticText(self, label="Variable Code") self.variable_code_text_ctrl = wx.TextCtrl(self) @@ -38,6 +38,7 @@ def __init__(self, parent): self.speciation_combo.SetSelection(0) self.variable_type_combo.SetSelection(0) self.SetupScrolling() + self.current_variable_radio.SetValue(True) # Add components to sizer table_sizer = wx.BoxSizer() @@ -80,6 +81,7 @@ def __init__(self, parent): main_sizer.Add(self.current_variable_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(self.existing_variable_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.create_variable_radio, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) self.SetSizer(main_sizer) From 6ebd18ce3a11ff00e832d1dfb8ed714722018370 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Mon, 10 Oct 2016 15:47:13 -0600 Subject: [PATCH 025/158] update for new api version --- ODMTools.py | 2 +- odmtools/odmdata/__init__.py | 6 +++--- odmtools/odmservices/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ODMTools.py b/ODMTools.py index 4b2ec5a..5774602 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -20,7 +20,7 @@ import pyodbc import pymysql -import psycopg2 +#import psycopg2 tool = LoggerTool() logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 79e14ac..41deff5 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -31,9 +31,9 @@ # Variable = ODM.Variable # VerticalDatumCV=ODM.VerticalDatumCV # returnDict = ODM.returnDict -from api.ODM1_1_1.services.series_service import ODM#, refreshDB -from api.ODMconnection import SessionFactory -from api.ODM2.models import change_schema +from odm2api.ODM1_1_1.services.series_service import ODM#, refreshDB +from odm2api.ODMconnection import SessionFactory +from odm2api.ODM2.models import _changeSchema as change_schema from odmtools.odmdata.memory_database import MemoryDatabase diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 1d39644..f15d43f 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -9,7 +9,7 @@ # import pyodbc # #import psycopg2 -from api.ODM1_1_1.services import SeriesService, EditService, ExportService#, , CVService, +from odm2api.ODM1_1_1.services import SeriesService, EditService, ExportService#, , CVService, from service_manager import ServiceManager __all__ = [ From 1707408725a0a84263aedf5faa69c170a75f3dc6 Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Tue, 11 Oct 2016 15:03:47 -0600 Subject: [PATCH 026/158] update code to use dbconnection from the api --- odmtools/gui/frmODMTools.py | 12 +-- odmtools/odmdata/__init__.py | 12 ++- odmtools/odmdata/memory_database.py | 4 +- odmtools/odmservices/service_manager.py | 125 ++++-------------------- setup/make.py | 2 +- 5 files changed, 35 insertions(+), 120 deletions(-) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index ecea071..8729a35 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -126,15 +126,11 @@ def _init_database(self, quit_if_cancel=True): while True: ## Database connection is valid, therefore proceed through the rest of the program - if newConnection: - conn_dict = newConnection - else: - conn_dict = self.service_manager.is_valid_connection() - if conn_dict: - # conn_dict = None + if self.service_manager.is_valid_connection(): + conn_dict = None - series_service = self.createService(conn_dict) - # conn_dict = self.service_manager.get_current_conn_dict() + series_service = self.createService() + conn_dict = self.service_manager.get_current_conn_dict() if self.servicesValid(series_service): self.service_manager.add_connection(conn_dict) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 41deff5..80b2cba 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -31,11 +31,11 @@ # Variable = ODM.Variable # VerticalDatumCV=ODM.VerticalDatumCV # returnDict = ODM.returnDict -from odm2api.ODM1_1_1.services.series_service import ODM#, refreshDB -from odm2api.ODMconnection import SessionFactory +from odm2api.ODM1_1_1.services import SeriesService#, refreshDB +from odm2api.ODMconnection import SessionFactory, dbconnection from odm2api.ODM2.models import _changeSchema as change_schema from odmtools.odmdata.memory_database import MemoryDatabase - +ODM = SeriesService.ODM from collections import OrderedDict def returnDict(): @@ -54,11 +54,13 @@ def returnDict(): ] return OrderedDict(zip(keys, values)) __all__=[ - 'SessionFactory', + #'SessionFactory', 'refreshDB', 'change_schema', 'returnDict', - 'ODM', + #'ODM', 'MemoryDatabase', 'returnDict' + 'SeriesService' + 'dbconnection' ] diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 2490e72..861e32c 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -5,8 +5,8 @@ from odmtools.common.logger import LoggerTool from odmtools.odmservices import SeriesService from odmtools.odmservices import ServiceManager -from odmtools.odmdata import ODM - +from odmtools.odmdata import SeriesService#ODM +ODM = SeriesService.ODM # tool = LoggerTool() # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) logger =logging.getLogger('main') diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 6fa1b3b..8c9cd7d 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,7 +8,8 @@ from odmtools.odmservices import SeriesService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SessionFactory, change_schema, ODM#, refreshDB +from odmtools.odmdata import SeriesService, dbconnection #ODM#, refreshDBSessionFactory, + @@ -44,6 +45,7 @@ def __init__(self, debug=False, conn_dict = None): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] + line_dict['version']= line[5] self._conn_dicts.append(line_dict) else: self._conn_dicts.append(conn_dict) @@ -84,115 +86,30 @@ def add_connection(self, conn_dict): # logger.error("Unable to save connection due to invalid connection to database") # return False - @staticmethod - def _getSchema(engine): - from sqlalchemy.engine import reflection - - insp=reflection.Inspector.from_engine(engine) - - for name in insp.get_schema_names(): - if 'odm2'== name.lower(): - return name - else: - return insp.default_schema_name - - @classmethod - def _setSchema(self, engine): - - s = self._getSchema(engine) - change_schema(s) - - - @classmethod - def testEngine(self, connection_string): - - s = SessionFactory(connection_string, echo=False) - try: - # s.ms_test_Session().query(Variable1).limit(1).first() - s.test_Session().query(ODM.Variable.code).limit(1).first() - - - except Exception as e: - print "Connection was unsuccessful ", e.message - return False - return True - # def is_valid_connection(self): - # if self._current_conn_dict: - # conn_string = self._build_connection_string(self._current_conn_dict) - # logger.debug("Conn_string: %s" % conn_string) - # try: - # if self.testEngine(conn_string): - # return self._current_conn_dict - # - # return None - - def is_valid_connection(self): - if self._current_conn_dict: - conn_string = self._build_connection_string(self._current_conn_dict) - logger.debug("Conn_string: %s" % conn_string) - dbtype = float(self._current_conn_dict['version']) - #dbtype =1.1 - #refreshDB(dbtype) - - try: - if self.testEngine(conn_string): - return self._current_conn_dict - except Exception as e: - logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) - return None - - def test_connection(self, conn_dict): - try: - conn_string = self._build_connection_string(conn_dict) - - dbtype = float(conn_dict['version']) - #dbtype =1.1 - #refreshDB(dbtype) - if self.testEngine(conn_string):# and self.get_db_version(conn_string) == '1.1.1': - - return True - except SQLAlchemyError as e: - logger.error("SQLAlchemy Error: %s" % e.message) - raise e - except Exception as e: - logger.error("Error: %s" % e) - raise e - return False def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] - # Create and return services based on the currently active connection - # def get_db_version_dict(self, conn_dict): - # conn_string = self._build_connection_string(conn_dict) - # self.get_db_version(conn_string) - - # def get_db_version(self, conn_string): - # if isinstance(conn_string, dict): - # conn_string = self._build_connection_string(conn_string) - # service = SeriesService(conn_string) - # #if not self.version: - # try: - # self.version = service.get_db_version() - # except Exception as e: - # logger.error("Exception: %s" % e.message) - # return None - # return self.version def get_series_service(self, conn_dict=None, conn_string=""): - version = 1.1 - if conn_dict: - conn_string = self._build_connection_string(conn_dict) - #self._current_conn_dict = conn_dict - - version = float(conn_dict['version']) - elif not conn_dict and not conn_string: - conn_string = self._build_connection_string(self._current_conn_dict) - version = float(self._current_conn_dict['version']) - - sf = SessionFactory(conn_string, self.debug, version = version) - ss= SeriesService(sf) - ss.refreshDB(sf.version) + if not conn_dict and not conn_string: + conn_dict = self._current_conn_dict + conn = dbconnection.createConnection(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], + conn_dict['password'], conn_dict['version']) + + # version = 1.1 + # if conn_dict: + # conn_string = self._build_connection_string(conn_dict) + # #self._current_conn_dict = conn_dict + # + # version = float(conn_dict['version']) + # elif not conn_dict and not conn_string: + # conn_string = self._build_connection_string(self._current_conn_dict) + # version = float(self._current_conn_dict['version']) + # + # sf = SessionFactory(conn_string, self.debug, version = version) + ss= SeriesService(conn) + ss.refreshDB(conn.version) return ss # def get_cv_service(self): diff --git a/setup/make.py b/setup/make.py index 820d0c8..b4ccbf6 100644 --- a/setup/make.py +++ b/setup/make.py @@ -160,7 +160,7 @@ def run_pyinstaller(console=False): '--version-file=%s ' % VERSION_FILE + '--onedir ' # '--onefile ' + - '--exclude= + #'--exclude= '--noconfirm ' + APP_FILE) else: ## Non Console Version From 5bf036c0effdb5baf3aa81adf54cefd675f9096c Mon Sep 17 00:00:00 2001 From: Stephanie Reeder Date: Tue, 11 Oct 2016 17:29:32 -0600 Subject: [PATCH 027/158] get odmtools connecting to odm2 database --- odmtools/gui/frmODMTools.py | 2 +- odmtools/odmdata/memory_database.py | 4 +- odmtools/odmservices/service_manager.py | 83 ++++++++----------------- 3 files changed, 29 insertions(+), 60 deletions(-) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index 8729a35..a153bbd 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -465,7 +465,7 @@ def createService(self, conn_dict=""): series_service= self.service_manager.get_series_service(conn_dict=conn_dict)#=connection) - series_service.refreshDB(conn_dict['version']) + return series_service diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 861e32c..b10d40a 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -24,7 +24,7 @@ def __init__(self, taskserver=None): # Memory_service handles in memory database sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") - self.mem_service.refreshDB(1.1) + # TODO clean up closing of program # if taskserver is None: #numproc = cpu_count() @@ -36,7 +36,7 @@ def __init__(self, taskserver=None): def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") - self.mem_service.refreshDB(1.1) + def set_series_service(self, service): self.series_service = service diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 8c9cd7d..a190839 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -45,7 +45,7 @@ def __init__(self, debug=False, conn_dict = None): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] - line_dict['version']= line[5] + line_dict['version']= float(line[5]) if len(line)>5 else 1.1 self._conn_dicts.append(line_dict) else: self._conn_dicts.append(conn_dict) @@ -86,17 +86,39 @@ def add_connection(self, conn_dict): # logger.error("Unable to save connection due to invalid connection to database") # return False + def is_valid_connection(self): + + if self.get_current_conn_dict(): + #conn_string = self._build_connection_string(self._current_conn_dict) + #logger.debug("Conn_string: %s" % conn_string) + conn_dict = self.get_current_conn_dict() + try: + if dbconnection.isValidConnection(dbconnection.buildConnectionString(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], + conn_dict['password']), dbtype = conn_dict['version']): + return self.get_current_conn_dict() + except Exception as e: + logger.fatal( + "The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) + return None + return None + def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] def get_series_service(self, conn_dict=None, conn_string=""): - if not conn_dict and not conn_string: - conn_dict = self._current_conn_dict - conn = dbconnection.createConnection(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], + if not conn_dict: + conn_dict = self.get_current_conn_dict() + + if conn_string: + #todo how to get version from a connection string + conn = dbconnection.createConnectionFromString(conn_string, float(self.get_current_conn_dict()["version"])) + else: + conn = dbconnection.createConnection(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], conn_dict['password'], conn_dict['version']) + # version = 1.1 # if conn_dict: # conn_string = self._build_connection_string(conn_dict) @@ -117,7 +139,6 @@ def get_series_service(self, conn_dict=None, conn_string=""): # return CVService(SessionFactory(conn_string, self.debug)) def get_edit_service(self, series_id, connection): - return EditService(series_id, connection=connection, debug=self.debug) @@ -152,58 +173,6 @@ def _get_file(self, mode): return config_file - def _build_connection_string(self, conn_dict): - - self._connection_format = "%s+%s://%s:%s@%s/%s" - - if conn_dict['engine'] == 'mssql' and sys.platform != 'win32': - driver = "pyodbc" - quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;' % (conn_dict['address'], conn_dict['user'], - conn_dict['password'])) - # quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;DATABASE=%s' % - # (conn_dict['address'], conn_dict['user'], conn_dict['password'],conn_dict['db'], - # )) - conn_string = 'mssql+pyodbc:///?odbc_connect={}'.format(quoted) - - elif conn_dict['engine']=='sqlite': - connformat = "%s:///%s" - conn_string = connformat%(conn_dict['engine'], conn_dict['address']) - else: - if conn_dict['engine'] == 'mssql': - driver = "pyodbc" - conn = "%s+%s://%s:%s@%s/%s?driver=SQL+Server" - if "sqlncli11.dll" in os.listdir("C:\\Windows\\System32"): - conn = "%s+%s://%s:%s@%s/%s?driver=SQL+Server+Native+Client+11.0" - self._connection_format = conn - conn_string = self._connection_format % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], - conn_dict['db']) - elif conn_dict['engine'] == 'mysql': - driver = "pymysql" - conn_string = self.constringBuilder(conn_dict, driver) - elif conn_dict['engine'] == 'postgresql': - driver = "psycopg2" - conn_string = self.constringBuilder(conn_dict, driver) - else: - driver = "None" - conn_string = self.constringBuilder(conn_dict, driver) - - - # print "******", conn_string - return conn_string - - - - def constringBuilder(self, conn_dict, driver): - if conn_dict['password'] is None or not conn_dict['password']: - conn_string = self._connection_format_nopassword % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['address'], - conn_dict['db']) - else: - conn_string = self._connection_format % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], - conn_dict['db']) - return conn_string def _save_connections(self): f = self._get_file('w') From ba4cfd5fdf9cd79163b286e909bc4961287ab398 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 12 Oct 2016 12:16:58 -0600 Subject: [PATCH 028/158] creating variable --- odmtools/controller/WizardVariableController.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index f15f345..2038b5f 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -93,7 +93,18 @@ def get_variable(self): elif self.variable_view.create_variable_radio.GetValue(): # v = self.createdVar - pass + v = self.get_new_variable() + + return v + + def get_new_variable(self): + v = Variable() + v.code = self.variable_view.variable_code_text_ctrl.GetValue() if self.variable_view.variable_code_text_ctrl.GetValue() <> "" else None + v.name = self.variable_view.variable_name_combo.GetValue() if self.variable_view.variable_name_combo.GetValue() <> "" else None + v.speciation = self.variable_view.speciation_combo.GetValue() if self.variable_view.speciation_combo.GetValue() <> "" else None + v.variable_unit = self.service_manager.get_series_service() + v.no_data_value = self.variable_view.no_data_value_text_ctrl.GetValue() if self.variable_view.no_data_value_text_ctrl.GetValue() <> "" else None + # Need unit name, time support but neither of them are in the form... return v From 1e16a6413142a5279ce176da6ee796fad257fa27 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 12 Oct 2016 12:58:06 -0600 Subject: [PATCH 029/158] Updating the cv service Rename to ReadService. Mapped the methods to use the ReadOMD2 class in OMD2api --- .../{cv_service.py => ReadService.py} | 56 +++++-------------- odmtools/odmservices/__init__.py | 4 +- odmtools/odmservices/service_manager.py | 4 +- tests/test_odmservices/test_cv_service.py | 4 +- 4 files changed, 20 insertions(+), 48 deletions(-) rename odmtools/odmservices/{cv_service.py => ReadService.py} (51%) diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/ReadService.py similarity index 51% rename from odmtools/odmservices/cv_service.py rename to odmtools/odmservices/ReadService.py index 4b448d2..7b98d7b 100644 --- a/odmtools/odmservices/cv_service.py +++ b/odmtools/odmservices/ReadService.py @@ -1,25 +1,13 @@ -# CV imports from odmtools.odmdata import SessionFactory from odmtools.odmdata import VerticalDatumCV -from odmtools.odmdata import SiteTypeCV -from odmtools.odmdata import VariableNameCV -from odmtools.odmdata import SpeciationCV -from odmtools.odmdata import SampleMediumCV from odmtools.odmdata import ValueTypeCV -from odmtools.odmdata import DataTypeCV from odmtools.odmdata import GeneralCategoryCV -from odmtools.odmdata import CensorCodeCV -from odmtools.odmdata import TopicCategoryCV -from odmtools.odmdata import SampleTypeCV -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Qualifier from odmtools.odmdata import Unit from sqlalchemy import not_ from odm2api.ODM2.services.readService import ReadODM2 -class CVService(): # Rename to ReadService +class ReadService: # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection_string="", debug=False): self._session_factory = SessionFactory(connection_string, debug) @@ -27,62 +15,49 @@ def __init__(self, connection_string="", debug=False): self._debug = debug self.read_service = ReadODM2(self._session_factory, debug=self._debug) - # Controlled Vocabulary get methods - #return a list of all terms in the cv def get_vertical_datum_cvs(self): result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() return result def get_samples(self): - result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() - return result + return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) def get_site_type_cvs(self): - result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() - return result + return self.read_service.getCVs(type="Site Type") # OR return self.read_service.getCVs(type="Sampling Feature Type") def get_variable_name_cvs(self): - result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() - return result + return self.read_service.getCVs(type="Variable Name") def get_offset_type_cvs(self): - result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() - return result + return self.read_service.getCVs(type="Spatial Offset Type") def get_speciation_cvs(self): - result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() - return result - # return self.read_service.getCVs(type="Speciation") # Returns Error running Query, + return self.read_service.getCVs(type="Speciation") def get_sample_medium_cvs(self): - result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() - return result + return self.read_service.getCVs(type="Medium") def get_value_type_cvs(self): result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() return result def get_data_type_cvs(self): - result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() - return result + return self.read_service.getCVs(type="dataset type") def get_general_category_cvs(self): result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() return result def get_censor_code_cvs(self): - result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() - return result + return self.read_service.getCVs(type="censorcode") def get_sample_type_cvs(self): - result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() - return result + return self.read_service.getCVs(type="Sampling Feature Type") def get_units(self): - result = self._edit_session.query(Unit).all() - return result + return self.read_service.getUnits(ids=None, name=None, type=None) def get_units_not_uni(self): result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() @@ -94,16 +69,13 @@ def get_units_names(self): # return a single cv def get_unit_by_name(self, unit_name): - result = self._edit_session.query(Unit).filter_by(name=unit_name).first() - return result + return self.read_service.getUnits(name=unit_name) def get_unit_by_id(self, unit_id): - result = self._edit_session.query(Unit).filter_by(id=unit_id).first() - return result + return self.read_service.getUnits(ids=unit_id) def get_annotation_by_code(self, code): - self.read_service.getAnnotations(type=code) - return + return self.read_service.getAnnotations(type=code) def get_all_annotations(self): return self.read_service.getAnnotations(type=None) diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 6d161f5..3dac41e 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -1,6 +1,6 @@ from service_manager import ServiceManager from series_service import SeriesService -from cv_service import CVService +from ReadService import ReadService from edit_service import EditService from export_service import ExportService @@ -11,7 +11,7 @@ __all__ = [ 'EditService', - 'CVService', + 'ReadService', 'SeriesService', 'ExportService', 'ServiceManager', diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 142a5c0..df31fa5 100644 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,7 +8,7 @@ from odmtools.common.logger import LoggerTool from series_service import SeriesService -from cv_service import CVService +from ReadService import ReadService from edit_service import EditService from odmtools.controller import EditTools from export_service import ExportService @@ -153,7 +153,7 @@ def get_series_service(self, conn_dict=""): def get_cv_service(self): conn_string = self._build_connection_string(self._current_conn_dict) - return CVService(conn_string, self.debug) + return ReadService(conn_string, self.debug) def get_edit_service(self, series_id, connection): diff --git a/tests/test_odmservices/test_cv_service.py b/tests/test_odmservices/test_cv_service.py index 61c8ef5..184b74a 100644 --- a/tests/test_odmservices/test_cv_service.py +++ b/tests/test_odmservices/test_cv_service.py @@ -1,4 +1,4 @@ -from odmtools.odmservices import CVService +from odmtools.odmservices import ReadService from tests import test_util @@ -8,7 +8,7 @@ class TestCVService: def setup(self): self.connection_string = "sqlite:///:memory:" - self.cv_service = CVService(self.connection_string, debug=False) + self.cv_service = ReadService(self.connection_string, debug=False) self.session = self.cv_service._session_factory.getSession() engine = self.cv_service._session_factory.engine test_util.build_db(engine) From 9fdffd47484a1bd616df4538f3101df01e560529 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 12 Oct 2016 13:21:46 -0600 Subject: [PATCH 030/158] #293 Mapping the create methods to the new series service in omd2 This broke a few things due to the update --- odmtools/odmservices/series_service.py | 78 +++++++++---------- tests/test_odmservices/test_cv_service.py | 2 +- tests/test_odmservices/test_series_service.py | 1 - 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 834014d..42e4c64 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -6,14 +6,16 @@ from odmtools.odmdata import SessionFactory from odmtools.odmdata import Site -from odmtools.odmdata import Variable +# from odmtools.odmdata import Variable +from odm2api.ODM2.models import Variables from odmtools.odmdata import Unit from odmtools.odmdata import Series from odmtools.odmdata import DataValue from odmtools.odmdata import Qualifier from odmtools.odmdata import OffsetType from odmtools.odmdata import Sample -from odmtools.odmdata import Method +# from odmtools.odmdata import Method +from odm2api.ODM2.models import Methods from odmtools.odmdata import QualityControlLevel from odmtools.odmdata import ODMVersion from odmtools.common.logger import LoggerTool @@ -107,7 +109,7 @@ def get_used_variables(self): #create list of variables from the list of ids for var_id in var_ids: - Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) + Variables.append(self._edit_session.query(Variables).filter_by(id=var_id).first()) return Variables @@ -116,7 +118,7 @@ def get_all_variables(self): :return: List[Variables] """ - return self._edit_session.query(Variable).all() + return self._edit_session.query(Variables).all() def get_variable_by_id(self, variable_id): """ @@ -125,7 +127,7 @@ def get_variable_by_id(self, variable_id): :return: Variables """ try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() + return self._edit_session.query(Variables).filter_by(id=variable_id).first() except: return None @@ -136,7 +138,7 @@ def get_variable_by_code(self, variable_code): :return: Variables """ try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() + return self._edit_session.query(Variables).filter_by(code=variable_code).first() except: return None @@ -154,7 +156,7 @@ def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeU variables = [] for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) + variables.append(self._edit_session.query(Variables).filter_by(id=var_id).first()) return variables @@ -234,18 +236,18 @@ def get_qcl_by_code(self, qcl_code): # Method methods def get_all_methods(self): - return self._edit_session.query(Method).all() + return self._edit_session.query(Methods).all() def get_method_by_id(self, method_id): try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() + result = self._edit_session.query(Methods).filter_by(id=method_id).first() except: result = None return result def get_method_by_description(self, method_code): try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() + result = self._edit_session.query(Methods).filter_by(description=method_code).first() except: result = None logger.error("method not found") @@ -566,21 +568,19 @@ def create_method(self, description, link): :param link: :return: """ - meth = Method() - meth.description = description + method = Methods() + method.MethodDescription = description if link is not None: - meth.link = link + method.MethodLink = link - self.create_service.createMethod(method=meth) - return meth + return self.create_service.createMethod(method=method) def create_variable_by_var(self, var): """ :param var: Variable Object :return: """ - self.create_service.createVariable(var=var) - return var + return self.create_service.createVariable(var=var) def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, @@ -601,22 +601,23 @@ def create_variable( :param no_data_value: :return: """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - - self.create_service.createVariable(var=var) - return var + # var = Variable() + variable = Variables() + variable.VariableCode = code + variable.VariableNameCV = name + variable.SpeciationCV = speciation + # Commented lines indicate that Variables does not have such attributes + # var.variable_unit_id = variable_unit_id + # var.sample_medium = sample_medium + # var.value_type = value_type + # var.is_regular = is_regular + # var.time_support = time_support + # var.time_unit_id = time_unit_id + # var.data_type = data_type + # var.general_category = general_category + variable.NoDataValue = no_data_value + + return self.create_service.createVariable(var=variable) def create_qcl(self, code, definition, explanation): """ @@ -630,13 +631,10 @@ def create_qcl(self, code, definition, explanation): qcl.code = code qcl.definition = definition qcl.explanation = explanation - self.create_service.createProcessingLevel(proclevel=qcl) - - return qcl + return self.create_service.createProcessingLevel(proclevel=qcl) def create_annotation_by_anno(self, annotation): - self.create_service.create(annotation) - return annotation + return self.create_service.create(annotation) def create_annotation(self, code, description): """ @@ -779,7 +777,7 @@ def method_exists(self, m): :return: """ try: - result = self._edit_session.query(Method).filter_by(description=m.description).one() + result = self._edit_session.query(Methods).filter_by(description=m.description).one() return True except: return False @@ -791,7 +789,7 @@ def variable_exists(self, v): :return: """ try: - result = self._edit_session.query(Variable).filter_by(code=v.code, + result = self._edit_session.query(Variables).filter_by(code=v.code, name=v.name, speciation=v.speciation, variable_unit_id=v.variable_unit_id, sample_medium=v.sample_medium, diff --git a/tests/test_odmservices/test_cv_service.py b/tests/test_odmservices/test_cv_service.py index 184b74a..e3a6ccd 100644 --- a/tests/test_odmservices/test_cv_service.py +++ b/tests/test_odmservices/test_cv_service.py @@ -5,7 +5,7 @@ session = None -class TestCVService: +class TestReadService: def setup(self): self.connection_string = "sqlite:///:memory:" self.cv_service = ReadService(self.connection_string, debug=False) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index c73dfc0..512fa10 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -104,7 +104,6 @@ def test_get_all_sites(self): if isinstance(sites, list) and len(sites) > 0: assert site.code == sites[0].code - def test_get_site_by_id_fail(self): assert self.series_service.get_site_by_id(0) == None From 0dfa0c780ea1f1bbb08dc610179fa3f36fef4890 Mon Sep 17 00:00:00 2001 From: stephanie Date: Fri, 14 Oct 2016 12:39:13 -0600 Subject: [PATCH 031/158] load series selector --- odmtools/common/taskServer.py | 2 +- odmtools/controller/frmSeriesSelector.py | 25 +- odmtools/controller/olvSeriesSelector.py | 48 +- odmtools/gui/pageMethod.py | 7 +- odmtools/odmdata/__init__.py | 36 +- odmtools/odmdata/memory_database.py | 43 +- odmtools/odmdata/series.py | 150 -- odmtools/odmservices/__init__.py | 4 +- odmtools/odmservices/series_service.py | 1586 +++++++++-------- odmtools/odmservices/service_manager.py | 37 +- tests/test_gui/test_plotProbability.py | 4 +- tests/test_gui/test_pnlDataTable.py | 4 +- tests/test_odmdata/test_memory_db.py | 4 +- tests/test_odmservices/test_edit_service.py | 2 +- tests/test_odmservices/test_export_service.py | 4 +- tests/test_odmservices/test_series_service.py | 4 +- 16 files changed, 946 insertions(+), 1014 deletions(-) delete mode 100644 odmtools/odmdata/series.py diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index 0620112..c38f707 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -132,7 +132,7 @@ def worker(cls, dispatcher): connection = SeriesService("sqlite:///:memory:") df = task[1] logger.debug("Load series from db") - df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) + df.to_sql(name="DataValues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) logger.debug("done loading database") result = connection if task_type == "UpdateEditDF": diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index 4563190..c306e7d 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -69,7 +69,8 @@ def initTableSeries(self): self.memDB.set_series_service(self.series_service) object = self.series_service.get_all_series() - + cols = object[0].__dict__.keys() + self.tblSeries._buildColumns(cols) if object: self.tblSeries.SetObjects(object) else: @@ -140,13 +141,13 @@ def initSVBoxes(self): try: self.siteList = self.series_service.get_used_sites() for site in self.siteList: - self.cbSites.Append(site.code + '-' + site.name) + self.cbSites.Append("%s-%s"%(site.SamplingFeatureCode, site.SamplingFeatureName)) self.cbSites.SetSelection(0) - self.site_code = self.siteList[0].code + self.site_code = self.siteList[0].SamplingFeatureCode self.varList = self.series_service.get_used_variables() for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) self.cbVariables.SetSelection(0) except AttributeError as e: logger.error(e) @@ -340,12 +341,12 @@ def onCbSitesCombobox(self, event): """ if self.checkSite.GetValue(): - self.site_code = self.siteList[event.GetSelection()].code + self.site_code = self.siteList[event.GetSelection()].SamplingFeatureCode self.varList = self.series_service.get_variables_by_site_code(self.site_code) self.cbVariables.Clear() for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) self.cbVariables.SetSelection(0) if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): @@ -362,7 +363,7 @@ def onCbVariablesCombobox(self, event): """ if self.checkVariable.GetValue(): - self.variable_code = self.varList[event.GetSelection()].code + self.variable_code = self.varList[event.GetSelection()].VariableCode if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): self.site_code = None self.setFilter(site_code=self.site_code, var_code=self.variable_code) @@ -373,16 +374,16 @@ def siteAndVariables(self): :return: """ - self.site_code = self.siteList[self.cbSites.Selection].code + self.site_code = self.siteList[self.cbSites.Selection].VariableCode self.cbVariables.Clear() self.varList = self.series_service.get_variables_by_site_code(self.site_code) for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) self.cbVariables.SetSelection(0) try: - self.variable_code = self.varList[self.cbVariables.Selection].code + self.variable_code = self.varList[self.cbVariables.Selection].VariableCode self.setFilter(site_code=self.site_code, var_code=self.variable_code) self.cbVariables.Enabled = True self.cbSites.Enabled = True @@ -411,12 +412,12 @@ def variableOnly(self): self.cbVariables.Clear() self.varList = self.series_service.get_used_variables() for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) self.cbVariables.SetSelection(0) self.cbSites.Enabled = False self.cbVariables.Enabled = True - self.variable_code = self.varList[0].code + self.variable_code = self.varList[0].VariableCode self.setFilter(var_code=self.variable_code) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 91f6dc3..cf1de92 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -7,7 +7,6 @@ # from odmtools.common.logger import LoggerTool -from odmtools.odmdata import series # tool = LoggerTool() @@ -16,21 +15,25 @@ OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() from collections import OrderedDict -def returnDict(): - keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) +# def returnDict(): +# keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', +# 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', +# 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', +# 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', +# 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' +# ] +# values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', +# 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', +# 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', +# 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', +# 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', +# 'end_date_time_utc', 'value_count' +# ] +# return OrderedDict(zip(keys, values)) + + + +# def returnDict(): class clsSeriesTable(FastObjectListView): def __init__(self, *args, **kwargs): @@ -47,8 +50,8 @@ def __init__(self, *args, **kwargs): """Object being edited""" self.editingObject = None - self._buildColumns() - self.CreateCheckStateColumn() + #self._buildColumns() + def rowFormatter(listItem, point): listItem.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)) @@ -60,13 +63,16 @@ def onKeyPress(self, evt): """Ignores Keypresses""" pass - def _buildColumns(self): + def _buildColumns(self, columns): seriesColumns = [ - ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, + ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in returnDict().iteritems()] + for key in columns] + + self.SetColumns(seriesColumns) + self.CreateCheckStateColumn() """User can select series using the mouse to click on check boxes """ diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py index de59425..eb802ab 100644 --- a/odmtools/gui/pageMethod.py +++ b/odmtools/gui/pageMethod.py @@ -3,7 +3,8 @@ import wx import wx.grid import wx.richtext -from odmtools.odmdata import ODM +# from odmtools.odmdata import ODM +from odm2api.ODM2.models import Methods [wxID_PNLMETHOD, wxID_PNLMETHODSLISTCTRL1, wxID_PNLMETHODSRBCREATENEW, @@ -119,8 +120,8 @@ def getMethod(self): m= self.series_service.get_method_by_description(genmethod) if m is None: logger.debug("assigning new method description") - m = ODM.Method() - m.description = genmethod + m = Methods() + m.MethodDescription = genmethod elif self.rbSelect.Value: diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 80b2cba..d6dc342 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,41 +1,12 @@ +#from odm2api.ODM1_1_1.services import SeriesService#, refreshDB - - -#from api.versionSwitcher import ODM, refreshDB - - -# DataTypeCV=ODM.DataTypeCV -# DataValue=ODM.DataValue -# # GeneralCategoryCV=ODM.GeneralCategoryCV -# ISOMetadata=ODM.ISOMetadata -# LabMethod=ODM.LabMethod -# Method=ODM.Method -# OffsetType=ODM.OffsetType -# Qualifier=ODM.Qualifier -# QualityControlLevel=ODM.QualityControlLevel -# Sample =ODM.Sample -# SampledMediumCV= ODM.SampleMediumCV -# # SampleTypeCV=ODM.SampleTypeCV -# Series=ODM.Series -# Site= ODM.Site -# SiteType=ODM.SiteTypeCV -# Source =ODM.Source -# SpatialReferences=ODM.SpatialReference -# SpeciationCV=ODM.SpeciationCV -# # TopicCategoryCV=ODM.TopicCategoryCV -# Unit= ODM.Unit -# # ValueTypeCV=ODM.ValueTypeCV -# Variable = ODM.Variable -# VerticalDatumCV=ODM.VerticalDatumCV -# returnDict = ODM.returnDict -from odm2api.ODM1_1_1.services import SeriesService#, refreshDB from odm2api.ODMconnection import SessionFactory, dbconnection from odm2api.ODM2.models import _changeSchema as change_schema from odmtools.odmdata.memory_database import MemoryDatabase -ODM = SeriesService.ODM +#ODM = SeriesService.ODM from collections import OrderedDict def returnDict(): @@ -61,6 +32,7 @@ def returnDict(): #'ODM', 'MemoryDatabase', 'returnDict' - 'SeriesService' + #'SeriesService' + 'readService', 'createService', 'updateService', 'deleteService' 'dbconnection' ] diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index b10d40a..06ec67a 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -4,11 +4,13 @@ from odmtools.common.logger import LoggerTool from odmtools.odmservices import SeriesService -from odmtools.odmservices import ServiceManager -from odmtools.odmdata import SeriesService#ODM -ODM = SeriesService.ODM -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) +from odmtools.odmservices import ServiceManager, SeriesService + +# from odmtools.odmdata import SeriesService#ODM +# ODM = SeriesService.ODM +from odm2api.ODM2.models import TimeSeriesResultValues as DataValue + + logger =logging.getLogger('main') class MemoryDatabase(object): @@ -105,8 +107,8 @@ def update(self, updates): updates : list of dictionary that contains 2 items, id and value ''' - stmt = (ODM.DataValue.__table__.update(). - where(ODM.DataValue.local_date_time == bindparam('id')). + stmt = (DataValue.__table__.update(). + where(DataValue.ValueDateTime == bindparam('id')). values(DataValue=bindparam('value')) ) @@ -118,11 +120,11 @@ def update(self, updates): def updateValue(self, ids, operator, value): # query = DataValue.data_value+value if operator == '+': - query = ODM.DataValue.data_value + value + query = DataValue.DataValue + value elif operator == '-': - query = ODM.DataValue.data_value - value + query = DataValue.DataValue - value elif operator == '*': - query = ODM.DataValue.data_value * value + query = DataValue.DataValue * value elif operator == '=': query = value @@ -130,8 +132,8 @@ def updateValue(self, ids, operator, value): #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) for c in chunks: - q=self.mem_service._session.query(ODM.DataValue).filter(ODM.DataValue.local_date_time.in_(c)) - q.update({ODM.DataValue.data_value: query}, False) + q=self.mem_service._session.query(DataValue).filter(DataValue.ValueDateTime.in_(c)) + q.update({DataValue.DataValue: query}, False) #self.updateDF() @@ -143,11 +145,12 @@ def chunking(self, data): #break into chunks to get around sqlite's restriction. allowing user to send in only 999 arguments at once + #TODO update to work with odm2 def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: - self.mem_service._session.query(ODM.DataValue).filter(ODM.DataValue.local_date_time.in_(c))\ - .update({ODM.DataValue.qualifier_id: value}, False) + self.mem_service._session.query(DataValue).filter(DataValue.ValueDateTime.in_(c))\ + .update({DataValue.qualifier_id: value}, False) def delete(self, ids): @@ -161,7 +164,7 @@ def addPoints(self, points): """ Takes in a list of points and loads each point into the database """ - stmt = ODM.DataValue.__table__.insert() + stmt = DataValue.__table__.insert() if not isinstance(points, list): points = [points] @@ -228,6 +231,8 @@ def initEditValues(self, seriesID): index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") + +#TODO: update to work with ODM2 def changeSeriesIDs(self, var=None, qcl=None, method=None): """ @@ -237,17 +242,17 @@ def changeSeriesIDs(self, var=None, qcl=None, method=None): :return: """ - query = self.mem_service._session.query(ODM.DataValue) + query = self.mem_service._session.query(DataValue) if var is not None: logger.debug(var) - query.update({ODM.DataValue.variable_id: var}) + query.update({DataValue.variable_id: var}) if method is not None: logger.debug(method) - query.update({ODM.DataValue.method_id: method}) + query.update({DataValue.method_id: method}) # check that the code is not zero # if qcl is not None and qcl.code != 0: if qcl is not None: logger.debug(qcl) - query.update({ODM.DataValue.quality_control_level_id: qcl}) + query.update({DataValue.quality_control_level_id: qcl}) diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py deleted file mode 100644 index 7688555..0000000 --- a/odmtools/odmdata/series.py +++ /dev/null @@ -1,150 +0,0 @@ -from collections import OrderedDict # Requires Python 2.7 >= - -from sqlalchemy import Column, Integer, ForeignKey, String, Float, DateTime -from sqlalchemy.orm import relationship - -from base import Base -from site import Site -from variable import Variable -from method import Method -from source import Source -from quality_control_level import QualityControlLevel - -def copy_series(from_series): - new = Series() - new.site_id = from_series.site_id - new.site_code = from_series.site_code - new.site_name = from_series.site_name - new.variable_id = from_series.variable_id - new.variable_code = from_series.variable_code - new.variable_name = from_series.variable_name - new.speciation = from_series.speciation - new.variable_units_id = from_series.variable_units_id - new.variable_units_name = from_series.variable_units_name - new.sample_medium = from_series.sample_medium - new.value_type = from_series.value_type - new.time_support = from_series.time_support - new.time_units_id = from_series.time_units_id - new.time_units_name = from_series.time_units_name - new.data_type = from_series.data_type - new.general_category = from_series.general_category - new.method_id = from_series.method_id - new.method_description = from_series.method_description - new.source_id = from_series.source_id - new.source_description = from_series.source_description - new.organization = from_series.organization - new.citation = from_series.citation - new.quality_control_level_id = from_series.quality_control_level_id - new.quality_control_level_code = from_series.quality_control_level_code - new.begin_date_time = from_series.begin_date_time - new.begin_date_time_utc = from_series.begin_date_time_utc - new.end_date_time_utc = from_series.end_date_time_utc - new.value_count = from_series.value_count - return new -class Series(Base): - __tablename__ = 'seriescatalog' - - id = Column('SeriesID', Integer, primary_key=True) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - site_code = Column('SiteCode', String) - site_name = Column('SiteName', String) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - variable_code = Column('VariableCode', String) - variable_name = Column('VariableName', String) - - speciation = Column('Speciation', String) - variable_units_id = Column('VariableUnitsID', Integer) - variable_units_name = Column('VariableUnitsName', String) - sample_medium = Column('SampleMedium', String) - value_type = Column('ValueType', String) - time_support = Column('TimeSupport', Float) - time_units_id = Column('TimeUnitsID', Integer) - time_units_name = Column('TimeUnitsName', String) - data_type = Column('DataType', String) - general_category = Column('GeneralCategory', String) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - method_description = Column('MethodDescription', String) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - source_description = Column('SourceDescription', String) - organization = Column('Organization', String) - citation = Column('Citation', String) - quality_control_level_id = Column('QualityControlLevelID', Integer, - ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - quality_control_level_code = Column('QualityControlLevelCode', String) - begin_date_time = Column('BeginDateTime', DateTime) - end_date_time = Column('EndDateTime', DateTime) - begin_date_time_utc = Column('BeginDateTimeUTC', DateTime) - end_date_time_utc = Column('EndDateTimeUTC', DateTime) - value_count = Column('ValueCount', Integer) - - data_values = relationship("DataValue", - primaryjoin="and_(DataValue.site_id == Series.site_id, " - "DataValue.variable_id == Series.variable_id, " - "DataValue.method_id == Series.method_id, " - "DataValue.source_id == Series.source_id, " - "DataValue.quality_control_level_id == Series.quality_control_level_id)", - foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", - order_by="DataValue.local_date_time", - backref="series") - - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - - def __repr__(self): - return "" % (self.id, self.site_name, self.variable_code, self.variable_name) - - def __eq__(self, other) : - # return self.__dict__ == other.__dict__ - return [self.id, self.site_id, self.site_code, self.site_name, self.variable_id, self.variable_code, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.quality_control_level_code, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] ==\ - [other.id, other.site_id, other.site_code, other.site_name, other.variable_id, other.variable_code, - other.variable_name, other.speciation, other.variable_units_id, other.variable_units_name, - other.sample_medium, other.value_type, other.time_support, other.time_units_id, other.time_units_name, - other.data_type, other.general_category, other.method_id, other.method_description, - other.source_id, other.source_description, other.organization, other.citation, - other.quality_control_level_id, other.quality_control_level_code, other.begin_date_time, - other.end_date_time, other.begin_date_time_utc, other.end_date_time_utc, other.value_count] - - - def get_table_columns(self): - return self.__table__.columns.keys() - - def list_repr(self): - return [self.id, self.site_code, self.variable_code, self.quality_control_level_code, - self.site_id, self.site_name, self.variable_id, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] - -def returnDict(): - keys = ['SeriesID', 'SiteCode','VariableCode','QualityControlLevelCode', - 'SiteID', 'SiteName', 'VariableID', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_code','variable_code','quality_control_level_code', - 'site_id', 'site_name', 'variable_id', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) - - - diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index f15d43f..22e89c4 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -9,9 +9,11 @@ # import pyodbc # #import psycopg2 -from odm2api.ODM1_1_1.services import SeriesService, EditService, ExportService#, , CVService, +from odm2api.ODM1_1_1.services import EditService, ExportService#, , CVService, SeriesService, +from series_service import SeriesService from service_manager import ServiceManager + __all__ = [ 'EditService', #'CVService', diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 078e6fe..34b3e17 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -3,38 +3,36 @@ from sqlalchemy import distinct, func - -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import Site -from odmtools.odmdata import Variable -from odmtools.odmdata import Unit -from odmtools.odmdata import Series -from odmtools.odmdata import DataValue -from odmtools.odmdata import Qualifier -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Method -from odmtools.odmdata import QualityControlLevel -from odmtools.odmdata import ODMVersion +from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 +from odm2api import serviceBase +from odm2api.ODM2.models import * from odmtools.common.logger import LoggerTool import pandas as pd -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) + logger =logging.getLogger('main') -class SeriesService(): +class SeriesService(serviceBase): + # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug + def __init__(self, connection, debug=False): + + serviceBase.__init__(self, connection, debug) + self.read = ReadODM2(self._session_factory) + self.update = UpdateODM2(self._session_factory) + self.delete = DeleteODM2(self._session_factory) + self.create = CreateODM2(self._session_factory) + def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks + self.read.reset_session() + self.update.reset_session() + self.delete.reset_session() + self.create.reset_session() + + + - def get_db_version(self): - return self._edit_session.query(ODMVersion).first().version_number ##################### # @@ -42,770 +40,854 @@ def get_db_version(self): # ##################### - # Site methods - def get_all_sites(self): - """ - - :return: List[Sites] - """ - return self._edit_session.query(Site).order_by(Site.code).all() def get_used_sites(self): - """ - Return a list of all sites that are being referenced in the Series Catalog Table - :return: List[Sites] - """ try: - site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] + fas=[x[0] for x in self._session.query(distinct(Results.FeatureActionID)).all()] except: - site_ids = None - - if not site_ids: return None - Sites = [] - for site_id in site_ids: - Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) - - return Sites - + sf=[x[0] for x in self._session.query(distinct(FeatureActions.SamplingFeatureID)).filter(FeatureActions.FeatureActionID.in_(fas)).all()] - def get_site_by_id(self, site_id): - """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites - """ - try: - return self._edit_session.query(Site).filter_by(id=site_id).first() - except: - return None + sites = self.read.getSamplingFeatures(type = "site", ids = sf) + return sites - # Variables methods def get_used_variables(self): - """ - #get list of used variable ids - :return: List[Variables] - """ - - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] - except: - var_ids = None - - if not var_ids: - return None - - Variables = [] - - #create list of variables from the list of ids - for var_id in var_ids: - Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return Variables - - def get_all_variables(self): - """ - - :return: List[Variables] - """ - return self._edit_session.query(Variable).all() - - def get_variable_by_id(self, variable_id): - """ - - :param variable_id: int - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() - except: - return None - - def get_variable_by_code(self, variable_code): - """ - - :param variable_code: str - :return: Variables - """ - try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() - except: - return None - - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] - """ try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( - site_code=site_code).all()] - except: - var_ids = None - - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return variables - - # Unit methods - def get_all_units(self): - """ - - :return: List[Units] - """ - return self._edit_session.query(Unit).all() - - def get_unit_by_name(self, unit_name): - """ - - :param unit_name: str - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None - - def get_unit_by_id(self, unit_id): - """ - - :param unit_id: int - :return: Units - """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() + ids= [x[0] for x in self._session.query(distinct(Results.VariableID)).all()] except: return None + vars= self.read.getVariables(ids = ids) + return vars - def get_all_qualifiers(self): - """ - :return: List[Qualifiers] - """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result - - def get_qualifier_by_code(self, code): - """ - - :return: Qualifiers - """ - result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - return result - - def get_qualifiers_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() - - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() - - def get_qcl_by_id(self, qcl_id): - try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() - except: - return None - - def get_qcl_by_code(self, qcl_code): - try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() - except: - return None - - # Method methods - def get_all_methods(self): - return self._edit_session.query(Method).all() - - def get_method_by_id(self, method_id): - try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() - except: - result = None - return result - - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() - except: - result = None - logger.error("method not found") - return result - - def get_offset_types_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() - return self._edit_session.query(OffsetType).join(subquery).distinct().all() - - def get_samples_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.sample_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() - return self._edit_session.query(Sample).join(subquery).distinct().all() - - # Series Catalog methods def get_all_series(self): - """ - Returns all series as a modelObject - :return: List[Series] - """ - - #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) - return self._edit_session.query(Series).order_by(Series.id).all() + return self.read.getDetailedResultInfo('Time Series Coverage') - def get_series_by_site(self , site_id): + def get_variables_by_site_code(self, site_code): """ - - :param site_id: int - :return: List[Series] - """ - try: - selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() - return selectedSeries - except: - return None - - def get_series_by_id(self, series_id): - """ - - :param series_id: int - :return: Series + Finds all of variables at a site + :param site_code: str + :return: List[Variables] """ try: - return self._edit_session.query(Series).filter_by(id=series_id).first() - except Exception as e: - print e - return None + var_ids = [x[0] for x in + self._session.query(distinct(Results.VariableID)) \ + .filter(Results.FeatureActionID == FeatureActions.FeatureActionID) \ + .filter(FeatureActions.SamplingFeatureID == SamplingFeatures.SamplingFeatureID) \ + .filter(SamplingFeatures.SamplingFeatureCode == site_code) - def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: Series - """ - try: - return self._edit_session.query(Series).filter_by( - site_id=site_id, variable_id=var_id, method_id=method_id, - source_id=source_id, quality_control_level_id=qcl_id).first() + ] except: - return None - - def get_series_from_filter(self): - # Pass in probably a Series object, match it against the database - pass - - - #Data Value Methods - def get_values_by_series(self, series_id): - ''' - - :param series_id: Series id - :return: pandas dataframe - ''' - series= self.get_series_by_id(series_id) - if series: - q = self._edit_session.query(DataValue).filter_by( - site_id=series.site_id, - variable_id=series.variable_id, - method_id=series.method_id, - source_id=series.source_id, - quality_control_level_id=series.quality_control_level_id) - - query=q.statement.compile(dialect=self._session_factory.engine.dialect) - data= pd.read_sql_query(sql= query, - con = self._session_factory.engine, - params = query.params ) - #return data.set_index(data['LocalDateTime']) - return data - else: - return None - - def get_all_values_df(self): - """ - - :return: Pandas DataFrame object - """ - q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) - columns = list(data) - - columns.insert(0, columns.pop(columns.index("DataValue"))) - columns.insert(1, columns.pop(columns.index("LocalDateTime"))) - columns.insert(2, columns.pop(columns.index("QualifierID"))) - - data = data.ix[:, columns] - return data.set_index(data['LocalDateTime']) - - def get_all_values_list(self): - """ - - :return: - """ - result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - return [x.list_repr() for x in result] - - def get_all_values(self): - return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() - - @staticmethod - def calcSeason(row): - - month = int(row["Month"]) - - if month in [1, 2, 3]: - return 1 - elif month in[4, 5, 6]: - return 2 - elif month in [7, 8, 9]: - return 3 - elif month in [10, 11, 12]: - return 4 - - def get_all_plot_values(self): - """ - - :return: - """ - q = self._edit_session.query(DataValue.data_value.label('DataValue'), - DataValue.local_date_time.label('LocalDateTime'), - DataValue.censor_code.label('CensorCode'), - func.strftime('%m', DataValue.local_date_time).label('Month'), - func.strftime('%Y', DataValue.local_date_time).label('Year') - #DataValue.local_date_time.strftime('%m'), - #DataValue.local_date_time.strftime('%Y')) - ).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data.set_index(data['LocalDateTime']) - - def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): - """ - - :param seriesID: - :param noDataValue: - :param startDate: - :param endDate: - :return: - """ - series = self.get_series_by_id(seriesID) - - DataValues = [ - (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), - dv.local_date_time.strftime('%Y')) - for dv in series.data_values - if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate - ] - data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) - data.set_index(data['LocalDateTime'], inplace=True) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data - - - - def get_data_value_by_id(self, id): - """ - - :param id: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(id=id).first() - except: - return None - + var_ids = None -##################### +# def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits +# """ +# Finds all of variables at a site +# :param site_code: str +# :return: List[Variables] +# """ +# try: +# var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( +# site_code=site_code).all()] +# except: +# var_ids = None # -#Update functions +# variables = [] +# for var_id in var_ids: +# variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) # -##################### - def update_series(self, series): - """ - - :param series: - :return: - """ - merged_series = self._edit_session.merge(series) - self._edit_session.add(merged_series) - self._edit_session.commit() - - def update_dvs(self, dv_list): - """ - - :param dv_list: - :return: - """ - merged_dv_list = map(self._edit_session.merge, dv_list) - self._edit_session.add_all(merged_dv_list) - self._edit_session.commit() - -##################### +# return variables + +# Series Catalog methods +# def get_all_series(self): +# """ +# Returns all series as a modelObject +# :return: List[Series] +# """ # -#Create functions +# #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) +# return self._edit_session.query(Series).order_by(Series.id).all() # -##################### - def save_series(self, series, dvs): - """ Save to an Existing Series - :param series: - :param data_values: - :return: - """ - - if self.series_exists(series): - - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - logger.info("Existing File was overwritten with new information") - return True - else: - logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") - # there wasn't an existing file to overwrite - raise Exception("Series does not exist, unable to save. Please select 'Save As'") - - - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.info(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - logger.info("A new series was added to the database, series id: "+str(series.id)) - return True - - def save_values(self, values): - """ - - :param values: pandas dataframe - :return: - """ - values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) - - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - """ - - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Series() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - - self._edit_session.add(series) - self._edit_session.commit() - return series - - def create_method(self, description, link): - """ - - :param description: - :param link: - :return: - """ - meth = Method() - meth.description = description - if link is not None: - meth.link = link - - self._edit_session.add(meth) - self._edit_session.commit() - return meth - - def create_variable_by_var(self, var): - """ - - :param var: Variable Object - :return: - """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None - - def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): - """ - - :param code: - :param name: - :param speciation: - :param variable_unit_id: - :param sample_medium: - :param value_type: - :param is_regular: - :param time_support: - :param time_unit_id: - :param data_type: - :param general_category: - :param no_data_value: - :return: - """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - - self._edit_session.add(var) - self._edit_session.commit() - return var - - def create_qcl(self, code, definition, explanation): - """ - - :param code: - :param definition: - :param explanation: - :return: - """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - - self._edit_session.add(qcl) - self._edit_session.commit() - return qcl - - - def create_qualifier_by_qual(self, qualifier): - self._edit_session.add(qualifier) - self._edit_session.commit() - return qualifier - - def create_qualifier(self, code, description): - """ - - :param code: - :param description: - :return: - """ - qual = Qualifier() - qual.code = code - qual.description = description - - return self.create_qualifier_by_qual(qual) - -##################### +# def get_series_by_site(self , site_id): +# """ # -# Delete functions +# :param site_id: int +# :return: List[Series] +# """ +# try: +# selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() +# return selectedSeries +# except: +# return None # -##################### - - def delete_series(self, series): - """ - - :param series: - :return: - """ - try: - self.delete_values_by_series(series) - - delete_series = self._edit_session.merge(series) - self._edit_session.delete(delete_series) - self._edit_session.commit() - except Exception as e: - message = "series was not successfully deleted: %s" % e - print message - logger.error(message) - raise e - - - def delete_values_by_series(self, series, startdate = None): - """ - - :param series: - :return: - """ - try: - q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - variable_id = series.variable_id, - method_id = series.method_id, - source_id = series.source_id, - quality_control_level_id = series.quality_control_level_id) - if startdate is not None: - #start date indicates what day you should start deleting values. the values will delete to the end of the series - return q.filter(DataValue.local_date_time >= startdate).delete() - else: - return q.delete() - - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex - - def delete_dvs(self, id_list): - """ +# def get_series_by_id(self, series_id): +# """ +# +# :param series_id: int +# :return: Series +# """ +# try: +# return self._edit_session.query(Series).filter_by(id=series_id).first() +# except Exception as e: +# print e +# return None - :param id_list: list of datetimes - :return: - """ - try: - self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex -##################### # -#Exist functions +# # Site methods +# def get_all_sites(self): +# """ # -##################### - - - def series_exists(self, series): - """ - - :param series: - :return: - """ - return self.series_exists_quint( - series.site_id, - series.variable_id, - series.method_id, - series.source_id, - series.quality_control_level_id - ) - - def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - try: - result = self._edit_session.query(Series).filter_by( - site_id=site_id, - variable_id=var_id, - method_id=method_id, - source_id=source_id, - quality_control_level_id=qcl_id - ).one() - - return True - except: - return False - - def qcl_exists(self, q): - """ - - :param q: - :return: - """ - try: - result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() - return True - except: - - return False - - def method_exists(self, m): - """ - - :param m: - :return: - """ - try: - result = self._edit_session.query(Method).filter_by(description=m.description).one() - return True - except: - return False +# :return: List[Sites] +# """ +# return self._edit_session.query(Site).order_by(Site.code).all() +# +# +# def get_used_sites(self): +# """ +# Return a list of all sites that are being referenced in the Series Catalog Table +# :return: List[Sites] +# """ +# try: +# site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] +# except: +# site_ids = None +# +# if not site_ids: +# return None +# +# Sites = [] +# for site_id in site_ids: +# Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) +# +# return Sites - def variable_exists(self, v): - """ +# +# +# def get_site_by_id(self, site_id): +# """ +# return a Site object that has an id=site_id +# :param site_id: integer- the identification number of the site +# :return: Sites +# """ +# try: +# return self._edit_session.query(Site).filter_by(id=site_id).first() +# except: +# return None +# +# # Variables methods +# def get_used_variables(self): +# """ +# #get list of used variable ids +# :return: List[Variables] +# """ +# +# try: +# var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] +# except: +# var_ids = None +# +# if not var_ids: +# return None +# +# Variables = [] +# +# #create list of variables from the list of ids +# for var_id in var_ids: +# Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) +# +# return Variables +# +# def get_all_variables(self): +# """ +# +# :return: List[Variables] +# """ +# return self._edit_session.query(Variable).all() +# +# def get_variable_by_id(self, variable_id): +# """ +# +# :param variable_id: int +# :return: Variables +# """ +# try: +# return self._edit_session.query(Variable).filter_by(id=variable_id).first() +# except: +# return None +# +# def get_variable_by_code(self, variable_code): +# """ +# +# :param variable_code: str +# :return: Variables +# """ +# try: +# return self._edit_session.query(Variable).filter_by(code=variable_code).first() +# except: +# return None +# - :param v: - :return: - """ - try: - result = self._edit_session.query(Variable).filter_by(code=v.code, - name=v.name, speciation=v.speciation, - variable_unit_id=v.variable_unit_id, - sample_medium=v.sample_medium, - value_type=v.value_type, is_regular=v.is_regular, - time_support=v.time_support, - time_unit_id=v.time_unit_id, data_type=v.data_type, - general_category=v.general_category, - no_data_value=v.no_data_value).one() - return result - except: - return None \ No newline at end of file +# +# # Unit methods +# def get_all_units(self): +# """ +# +# :return: List[Units] +# """ +# return self._edit_session.query(Unit).all() +# +# def get_unit_by_name(self, unit_name): +# """ +# +# :param unit_name: str +# :return: Units +# """ +# try: +# return self._edit_session.query(Unit).filter_by(name=unit_name).first() +# except: +# return None +# +# def get_unit_by_id(self, unit_id): +# """ +# +# :param unit_id: int +# :return: Units +# """ +# try: +# return self._edit_session.query(Unit).filter_by(id=unit_id).first() +# except: +# return None +# +# +# def get_all_qualifiers(self): +# """ +# +# :return: List[Qualifiers] +# """ +# result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() +# return result +# +# def get_qualifier_by_code(self, code): +# """ +# +# :return: Qualifiers +# """ +# result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() +# return result +# +# def get_qualifiers_by_series_id(self, series_id): +# """ +# +# :param series_id: +# :return: +# """ +# subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( +# Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() +# return self._edit_session.query(Qualifier).join(subquery).distinct().all() +# +# #QCL methods +# def get_all_qcls(self): +# return self._edit_session.query(QualityControlLevel).all() +# +# def get_qcl_by_id(self, qcl_id): +# try: +# return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() +# except: +# return None +# +# def get_qcl_by_code(self, qcl_code): +# try: +# return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() +# except: +# return None +# +# # Method methods +# def get_all_methods(self): +# return self._edit_session.query(Method).all() +# +# def get_method_by_id(self, method_id): +# try: +# result = self._edit_session.query(Method).filter_by(id=method_id).first() +# except: +# result = None +# return result +# +# def get_method_by_description(self, method_code): +# try: +# result = self._edit_session.query(Method).filter_by(description=method_code).first() +# except: +# result = None +# logger.error("method not found") +# return result +# +# def get_offset_types_by_series_id(self, series_id): +# """ +# +# :param series_id: +# :return: +# """ +# subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( +# Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() +# return self._edit_session.query(OffsetType).join(subquery).distinct().all() +# +# def get_samples_by_series_id(self, series_id): +# """ +# +# :param series_id: +# :return: +# """ +# subquery = self._edit_session.query(DataValue.sample_id).outerjoin( +# Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() +# return self._edit_session.query(Sample).join(subquery).distinct().all() +# +# # Series Catalog methods +# def get_all_series(self): +# """ +# Returns all series as a modelObject +# :return: List[Series] +# """ +# +# #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) +# return self._edit_session.query(Series).order_by(Series.id).all() +# +# def get_series_by_site(self , site_id): +# """ +# +# :param site_id: int +# :return: List[Series] +# """ +# try: +# selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() +# return selectedSeries +# except: +# return None +# +# def get_series_by_id(self, series_id): +# """ +# +# :param series_id: int +# :return: Series +# """ +# try: +# return self._edit_session.query(Series).filter_by(id=series_id).first() +# except Exception as e: +# print e +# return None +# +# def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): +# """ +# +# :param site_id: +# :param var_id: +# :param method_id: +# :param source_id: +# :param qcl_id: +# :return: Series +# """ +# try: +# return self._edit_session.query(Series).filter_by( +# site_id=site_id, variable_id=var_id, method_id=method_id, +# source_id=source_id, quality_control_level_id=qcl_id).first() +# except: +# return None +# +# def get_series_from_filter(self): +# # Pass in probably a Series object, match it against the database +# pass +# +# +# #Data Value Methods +# def get_values_by_series(self, series_id): +# ''' +# +# :param series_id: Series id +# :return: pandas dataframe +# ''' +# series= self.get_series_by_id(series_id) +# if series: +# q = self._edit_session.query(DataValue).filter_by( +# site_id=series.site_id, +# variable_id=series.variable_id, +# method_id=series.method_id, +# source_id=series.source_id, +# quality_control_level_id=series.quality_control_level_id) +# +# query=q.statement.compile(dialect=self._session_factory.engine.dialect) +# data= pd.read_sql_query(sql= query, +# con = self._session_factory.engine, +# params = query.params ) +# #return data.set_index(data['LocalDateTime']) +# return data +# else: +# return None +# +# def get_all_values_df(self): +# """ +# +# :return: Pandas DataFrame object +# """ +# q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) +# query = q.statement.compile(dialect=self._session_factory.engine.dialect) +# data = pd.read_sql_query(sql=query, con=self._session_factory.engine, +# params=query.params) +# columns = list(data) +# +# columns.insert(0, columns.pop(columns.index("DataValue"))) +# columns.insert(1, columns.pop(columns.index("LocalDateTime"))) +# columns.insert(2, columns.pop(columns.index("QualifierID"))) +# +# data = data.ix[:, columns] +# return data.set_index(data['LocalDateTime']) +# +# def get_all_values_list(self): +# """ +# +# :return: +# """ +# result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() +# return [x.list_repr() for x in result] +# +# def get_all_values(self): +# return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() +# +# @staticmethod +# def calcSeason(row): +# +# month = int(row["Month"]) +# +# if month in [1, 2, 3]: +# return 1 +# elif month in[4, 5, 6]: +# return 2 +# elif month in [7, 8, 9]: +# return 3 +# elif month in [10, 11, 12]: +# return 4 +# +# def get_all_plot_values(self): +# """ +# +# :return: +# """ +# q = self._edit_session.query(DataValue.data_value.label('DataValue'), +# DataValue.local_date_time.label('LocalDateTime'), +# DataValue.censor_code.label('CensorCode'), +# func.strftime('%m', DataValue.local_date_time).label('Month'), +# func.strftime('%Y', DataValue.local_date_time).label('Year') +# #DataValue.local_date_time.strftime('%m'), +# #DataValue.local_date_time.strftime('%Y')) +# ).order_by(DataValue.local_date_time) +# query = q.statement.compile(dialect=self._session_factory.engine.dialect) +# data = pd.read_sql_query(sql=query, +# con=self._session_factory.engine, +# params=query.params) +# data["Season"] = data.apply(self.calcSeason, axis=1) +# return data.set_index(data['LocalDateTime']) +# +# def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): +# """ +# +# :param seriesID: +# :param noDataValue: +# :param startDate: +# :param endDate: +# :return: +# """ +# series = self.get_series_by_id(seriesID) +# +# DataValues = [ +# (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), +# dv.local_date_time.strftime('%Y')) +# for dv in series.data_values +# if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate +# ] +# data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) +# data.set_index(data['LocalDateTime'], inplace=True) +# data["Season"] = data.apply(self.calcSeason, axis=1) +# return data +# +# +# +# def get_data_value_by_id(self, id): +# """ +# +# :param id: +# :return: +# """ +# try: +# return self._edit_session.query(DataValue).filter_by(id=id).first() +# except: +# return None +# +# +# +# +# ##################### +# # +# #Update functions +# # +# ##################### +# def update_series(self, series): +# """ +# +# :param series: +# :return: +# """ +# merged_series = self._edit_session.merge(series) +# self._edit_session.add(merged_series) +# self._edit_session.commit() +# +# def update_dvs(self, dv_list): +# """ +# +# :param dv_list: +# :return: +# """ +# merged_dv_list = map(self._edit_session.merge, dv_list) +# self._edit_session.add_all(merged_dv_list) +# self._edit_session.commit() +# +# ##################### +# # +# #Create functions +# # +# ##################### +# def save_series(self, series, dvs): +# """ Save to an Existing Series +# :param series: +# :param data_values: +# :return: +# """ +# +# if self.series_exists(series): +# +# try: +# self._edit_session.add(series) +# self._edit_session.commit() +# self.save_values(dvs) +# except Exception as e: +# self._edit_session.rollback() +# raise e +# logger.info("Existing File was overwritten with new information") +# return True +# else: +# logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") +# # there wasn't an existing file to overwrite +# raise Exception("Series does not exist, unable to save. Please select 'Save As'") +# +# +# def save_new_series(self, series, dvs): +# """ Create as a new catalog entry +# :param series: +# :param data_values: +# :return: +# """ +# # Save As case +# if self.series_exists(series): +# msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" +# logger.info(msg) +# raise Exception(msg) +# else: +# try: +# self._edit_session.add(series) +# self._edit_session.commit() +# self.save_values(dvs) +# #self._edit_session.add_all(dvs) +# except Exception as e: +# self._edit_session.rollback() +# raise e +# +# logger.info("A new series was added to the database, series id: "+str(series.id)) +# return True +# +# def save_values(self, values): +# """ +# +# :param values: pandas dataframe +# :return: +# """ +# values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) +# +# def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): +# """ +# +# :param data_values: +# :param site_id: +# :param variable_id: +# :param method_id: +# :param source_id: +# :param qcl_id: +# :return: +# """ +# self.update_dvs(data_values) +# series = Series() +# series.site_id = site_id +# series.variable_id = variable_id +# series.method_id = method_id +# series.source_id = source_id +# series.quality_control_level_id = qcl_id +# +# self._edit_session.add(series) +# self._edit_session.commit() +# return series +# +# def create_method(self, description, link): +# """ +# +# :param description: +# :param link: +# :return: +# """ +# meth = Method() +# meth.description = description +# if link is not None: +# meth.link = link +# +# self._edit_session.add(meth) +# self._edit_session.commit() +# return meth +# +# def create_variable_by_var(self, var): +# """ +# +# :param var: Variable Object +# :return: +# """ +# try: +# self._edit_session.add(var) +# self._edit_session.commit() +# return var +# except: +# return None +# +# def create_variable( +# self, code, name, speciation, variable_unit_id, sample_medium, +# value_type, is_regular, time_support, time_unit_id, data_type, +# general_category, no_data_value): +# """ +# +# :param code: +# :param name: +# :param speciation: +# :param variable_unit_id: +# :param sample_medium: +# :param value_type: +# :param is_regular: +# :param time_support: +# :param time_unit_id: +# :param data_type: +# :param general_category: +# :param no_data_value: +# :return: +# """ +# var = Variable() +# var.code = code +# var.name = name +# var.speciation = speciation +# var.variable_unit_id = variable_unit_id +# var.sample_medium = sample_medium +# var.value_type = value_type +# var.is_regular = is_regular +# var.time_support = time_support +# var.time_unit_id = time_unit_id +# var.data_type = data_type +# var.general_category = general_category +# var.no_data_value = no_data_value +# +# self._edit_session.add(var) +# self._edit_session.commit() +# return var +# +# def create_qcl(self, code, definition, explanation): +# """ +# +# :param code: +# :param definition: +# :param explanation: +# :return: +# """ +# qcl = QualityControlLevel() +# qcl.code = code +# qcl.definition = definition +# qcl.explanation = explanation +# +# self._edit_session.add(qcl) +# self._edit_session.commit() +# return qcl +# +# +# def create_qualifier_by_qual(self, qualifier): +# self._edit_session.add(qualifier) +# self._edit_session.commit() +# return qualifier +# +# def create_qualifier(self, code, description): +# """ +# +# :param code: +# :param description: +# :return: +# """ +# qual = Qualifier() +# qual.code = code +# qual.description = description +# +# return self.create_qualifier_by_qual(qual) +# +# ##################### +# # +# # Delete functions +# # +# ##################### +# +# def delete_series(self, series): +# """ +# +# :param series: +# :return: +# """ +# try: +# self.delete_values_by_series(series) +# +# delete_series = self._edit_session.merge(series) +# self._edit_session.delete(delete_series) +# self._edit_session.commit() +# except Exception as e: +# message = "series was not successfully deleted: %s" % e +# print message +# logger.error(message) +# raise e +# +# +# def delete_values_by_series(self, series, startdate = None): +# """ +# +# :param series: +# :return: +# """ +# try: +# q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, +# variable_id = series.variable_id, +# method_id = series.method_id, +# source_id = series.source_id, +# quality_control_level_id = series.quality_control_level_id) +# if startdate is not None: +# #start date indicates what day you should start deleting values. the values will delete to the end of the series +# return q.filter(DataValue.local_date_time >= startdate).delete() +# else: +# return q.delete() +# +# except Exception as ex: +# message = "Values were not successfully deleted: %s" % ex +# print message +# logger.error(message) +# raise ex +# +# def delete_dvs(self, id_list): +# """ +# +# :param id_list: list of datetimes +# :return: +# """ +# try: +# self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) +# except Exception as ex: +# message = "Values were not successfully deleted: %s" % ex +# print message +# logger.error(message) +# raise ex +# +# ##################### +# # +# #Exist functions +# # +# ##################### +# +# +# def series_exists(self, series): +# """ +# +# :param series: +# :return: +# """ +# return self.series_exists_quint( +# series.site_id, +# series.variable_id, +# series.method_id, +# series.source_id, +# series.quality_control_level_id +# ) +# +# def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): +# """ +# +# :param site_id: +# :param var_id: +# :param method_id: +# :param source_id: +# :param qcl_id: +# :return: +# """ +# try: +# result = self._edit_session.query(Series).filter_by( +# site_id=site_id, +# variable_id=var_id, +# method_id=method_id, +# source_id=source_id, +# quality_control_level_id=qcl_id +# ).one() +# +# return True +# except: +# return False +# +# def qcl_exists(self, q): +# """ +# +# :param q: +# :return: +# """ +# try: +# result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() +# return True +# except: +# +# return False +# +# def method_exists(self, m): +# """ +# +# :param m: +# :return: +# """ +# try: +# result = self._edit_session.query(Method).filter_by(description=m.description).one() +# return True +# except: +# return False +# +# def variable_exists(self, v): +# """ +# +# :param v: +# :return: +# """ +# try: +# result = self._edit_session.query(Variable).filter_by(code=v.code, +# name=v.name, speciation=v.speciation, +# variable_unit_id=v.variable_unit_id, +# sample_medium=v.sample_medium, +# value_type=v.value_type, is_regular=v.is_regular, +# time_support=v.time_support, +# time_unit_id=v.time_unit_id, data_type=v.data_type, +# general_category=v.general_category, +# no_data_value=v.no_data_value).one() +# return result +# except: +# return None \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index a190839..7d9837c 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -8,7 +8,7 @@ from odmtools.odmservices import SeriesService, EditService, ExportService from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata import SeriesService, dbconnection #ODM#, refreshDBSessionFactory, +from odmtools.odmdata import dbconnection #ODM @@ -86,20 +86,34 @@ def add_connection(self, conn_dict): # logger.error("Unable to save connection due to invalid connection to database") # return False + def test_connection(self, conn_dict): + + try: + if dbconnection.isValidConnection( + dbconnection.buildConnectionString(conn_dict['engine'], conn_dict['address'], conn_dict['db'], + conn_dict['user'], + conn_dict['password']), dbtype=conn_dict['version']): + return self.get_current_conn_dict() + # except Exception as e: + # logger.fatal( + # "The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) + # return None + except SQLAlchemyError as e: + logger.error("SQLAlchemy Error: %s" % e.message) + raise e + except Exception as e: + logger.error("The database is not accessible please enter a new connection. Error: %s" % e.message) + raise e + + def is_valid_connection(self): + # conn_string = self._build_connection_string(self._current_conn_dict) + # logger.debug("Conn_string: %s" % conn_string) if self.get_current_conn_dict(): - #conn_string = self._build_connection_string(self._current_conn_dict) - #logger.debug("Conn_string: %s" % conn_string) conn_dict = self.get_current_conn_dict() - try: - if dbconnection.isValidConnection(dbconnection.buildConnectionString(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], - conn_dict['password']), dbtype = conn_dict['version']): - return self.get_current_conn_dict() - except Exception as e: - logger.fatal( - "The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) - return None + return self.test_connection(conn_dict) + return None @@ -131,7 +145,6 @@ def get_series_service(self, conn_dict=None, conn_string=""): # # sf = SessionFactory(conn_string, self.debug, version = version) ss= SeriesService(conn) - ss.refreshDB(conn.version) return ss # def get_cv_service(self): diff --git a/tests/test_gui/test_plotProbability.py b/tests/test_gui/test_plotProbability.py index ffac10b..e35cba7 100644 --- a/tests/test_gui/test_plotProbability.py +++ b/tests/test_gui/test_plotProbability.py @@ -23,8 +23,8 @@ def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine build_db(engine) self.memory_db = MemoryDatabase() diff --git a/tests/test_gui/test_pnlDataTable.py b/tests/test_gui/test_pnlDataTable.py index ad7004d..ca01e76 100644 --- a/tests/test_gui/test_pnlDataTable.py +++ b/tests/test_gui/test_pnlDataTable.py @@ -14,8 +14,8 @@ def setup(self): #set up remote Database self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) self.dvs_size = 100 diff --git a/tests/test_odmdata/test_memory_db.py b/tests/test_odmdata/test_memory_db.py index 4239295..d3b6cb3 100644 --- a/tests/test_odmdata/test_memory_db.py +++ b/tests/test_odmdata/test_memory_db.py @@ -10,8 +10,8 @@ class TestMemoryDB: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) self.memory_db = MemoryDatabase() diff --git a/tests/test_odmservices/test_edit_service.py b/tests/test_odmservices/test_edit_service.py index 341bc5c..ec56124 100644 --- a/tests/test_odmservices/test_edit_service.py +++ b/tests/test_odmservices/test_edit_service.py @@ -10,7 +10,7 @@ def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - engine = self.series_service._session_factory.engine + engine = self.series_service._connection.engine test_util.build_db(engine) self.memory_database = MemoryDatabase() diff --git a/tests/test_odmservices/test_export_service.py b/tests/test_odmservices/test_export_service.py index acc146f..54fc265 100644 --- a/tests/test_odmservices/test_export_service.py +++ b/tests/test_odmservices/test_export_service.py @@ -8,8 +8,8 @@ class TestExportService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) self.series = test_util.add_series(self.session) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index ebeffd5..78bf78f 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -8,8 +8,8 @@ class TestSeriesService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) """ @pytest.fixture(scope="class", autouse=True) From 07516a97834cb827be9a237a5cab523f86f4eb83 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 17 Oct 2016 13:15:25 -0600 Subject: [PATCH 032/158] Updated the acl to process level, and vertical datum to elevation datum --- odmtools/controller/logicEditTools.py | 2 +- odmtools/gui/wizSave.py | 2 +- odmtools/odmservices/ReadService.py | 6 ++---- odmtools/odmservices/edit_service.py | 2 +- odmtools/odmservices/series_service.py | 17 ++++++++--------- tests/test_odmservices/test_series_service.py | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index 2504bd8..8ee3814 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -379,7 +379,7 @@ def toggle_record(self, value): # Creates ################### def create_qcl(self, code, definition, explanation): - qcl = self._edit_service.create_qcl(code, definition, explanation) + qcl = self._edit_service.create_processing_level(code, definition, explanation) if self._record: self._script('new_qcl = series_service.get_qcl_by_id(%s)\n' % (qcl.id)) Publisher.sendMessage("scroll") diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index a8837d9..657afb4 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -380,7 +380,7 @@ def on_wizard_finished(self, event): else: QCL = self.record_service.get_qcl(QCL) else: - QCL = self.record_service.create_qcl(QCL.code, QCL.definition, QCL.explanation) + QCL = self.record_service.create_processing_level(QCL.code, QCL.definition, QCL.explanation) #if variable exists use its id if self.series_service.variable_exists(Variable): diff --git a/odmtools/odmservices/ReadService.py b/odmtools/odmservices/ReadService.py index 7b98d7b..e57dc2e 100644 --- a/odmtools/odmservices/ReadService.py +++ b/odmtools/odmservices/ReadService.py @@ -18,8 +18,7 @@ def __init__(self, connection_string="", debug=False): # Controlled Vocabulary get methods #return a list of all terms in the cv def get_vertical_datum_cvs(self): - result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() - return result + return self.read_service.getCVs(type="Elevation Datum") def get_samples(self): return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) @@ -47,8 +46,7 @@ def get_data_type_cvs(self): return self.read_service.getCVs(type="dataset type") def get_general_category_cvs(self): - result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() - return result + return self.read_service.getAnnotations(type="categoricalresultvalue") def get_censor_code_cvs(self): return self.read_service.getCVs(type="censorcode") diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 567b0dc..eb66c2b 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -594,7 +594,7 @@ def save_existing(self, var=None, method=None, qcl=None): return False def create_qcl(self, code, definition, explanation): - return self.memDB.series_service.create_qcl(code, definition, explanation) + return self.memDB.series_service.create_processing_level(code, definition, explanation) def create_method(self, description, link): return self.memDB.series_service.create_method(description, link) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 42e4c64..429bf2f 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -7,7 +7,7 @@ from odmtools.odmdata import SessionFactory from odmtools.odmdata import Site # from odmtools.odmdata import Variable -from odm2api.ODM2.models import Variables +from odm2api.ODM2.models import * from odmtools.odmdata import Unit from odmtools.odmdata import Series from odmtools.odmdata import DataValue @@ -22,7 +22,7 @@ import pandas as pd from odm2api.ODM2.services.createService import CreateODM2 from odm2api.ODM2.models import Annotations -from odm2api.ODM2.services.readService import ReadODM2 +from odm2api.ODM2.services.readService import * # tool = LoggerTool() @@ -619,7 +619,7 @@ def create_variable( return self.create_service.createVariable(var=variable) - def create_qcl(self, code, definition, explanation): + def create_processing_level(self, code, definition, explanation): """ qcl -> Processing Level in ODM2 :param code: @@ -627,18 +627,17 @@ def create_qcl(self, code, definition, explanation): :param explanation: :return: """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - return self.create_service.createProcessingLevel(proclevel=qcl) + procLevel = ProcessingLevels() + procLevel.ProcessingLevelCode = code + procLevel.Definition = definition + procLevel.Explanation = explanation + return self.create_service.createProcessingLevel(proclevel=procLevel) def create_annotation_by_anno(self, annotation): return self.create_service.create(annotation) def create_annotation(self, code, description): """ - :param code: :param description: :return: diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 512fa10..05efa88 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -359,7 +359,7 @@ def test_create_variable(self): assert variable.variable_unit_id == unit.id def test_create_qcl(self): - qcl = self.series_service.create_qcl("Code", "Definition", "Explanation") + qcl = self.series_service.create_processing_level("Code", "Definition", "Explanation") assert qcl.id != None assert qcl.code == "Code" From 919afc1efff78137fdb312bea6f469b354a2bd9a Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 17 Oct 2016 14:11:43 -0600 Subject: [PATCH 033/158] Converted most of the get functions in the series service to connect to omd2 Series, values, plot, and dataframes need to be converted --- odmtools/controller/WizardMethodController.py | 4 +- .../WizardProcessLevelController.py | 4 +- odmtools/gui/pageQCL.py | 2 +- odmtools/gui/wizSave.py | 2 +- odmtools/odmservices/edit_service.py | 2 +- odmtools/odmservices/series_service.py | 106 ++++++------------ tests/test_odmservices/test_series_service.py | 8 +- 7 files changed, 46 insertions(+), 82 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index e0c245d..9272987 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -64,7 +64,7 @@ def getMethod(self): m = Method() if self.method_view.auto_method_radio.GetValue(): description = "Values derived from ODM Tools Python" - m = self.series_service.get_method_by_description(description) + m = self.series_service.get_method_by_code(description) if m is None: m = Method() m.description = description @@ -72,7 +72,7 @@ def getMethod(self): index = self.method_view.existing_method_table.GetFirstSelected() desc = self.method_view.existing_method_table.GetItem(index, 0).GetText() - m = self.series_service.get_method_by_description(desc) + m = self.series_service.get_method_by_code(desc) elif self.method_view.create_method_radio.GetValue(): m.description = self.method_view.description_text_ctrl.GetValue() diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 76f3aed..f172453 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -39,7 +39,7 @@ def __set_create_proces_section(self, active): def __fetch_data(self): series_service = self.service_manager.get_series_service() - processes = series_service.get_all_qcls() + processes = series_service.get_all_processing_levels() data = [] for proc in processes: @@ -62,6 +62,6 @@ def getQCL(self): elif self.processing_level_view.existing_process_radio.GetValue(): selected_row = self.processing_level_view.existing_process_table.get_selected_row() code = selected_row[0] - q = self.service_manager.get_series_service().get_qcl_by_code(qcl_code=code) + q = self.service_manager.get_series_service().get_processing_level_by_code(proc_level_code=code) return q diff --git a/odmtools/gui/pageQCL.py b/odmtools/gui/pageQCL.py index 57f4970..044671c 100644 --- a/odmtools/gui/pageQCL.py +++ b/odmtools/gui/pageQCL.py @@ -122,7 +122,7 @@ def getQCL(self): logger.debug("lstQCL: %s" %(self.lstQCL)) code= self.lstQCL.GetItem(index, 0).GetText() logger.debug(code) - q= self.series_service.get_qcl_by_code(code) + q= self.series_service.get_processing_level_by_code(code) ## q.id = self.lstQCL.GetItem(index,3).GetText() ## q.code = self.lstQCL.GetItem(index, 0).GetText() diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 657afb4..d88d3af 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -48,7 +48,7 @@ def __init__(self, parent, title, series_service, qcl): self._init_data(self.panel.series_service) def _init_data(self, series): - qcl = series.get_all_qcls() + qcl = series.get_all_processing_levels() index = 0 for q, i in zip(qcl, range(len(qcl))): num_items = self.panel.lstQCL.GetItemCount() diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index eb66c2b..bcf52cf 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -284,7 +284,7 @@ def get_filter_list(self): return self._filter_list def get_qcl(self, qcl_id): - return self.memDB.series_service.get_qcl_by_id(qcl_id) + return self.memDB.series_service.get_processing_level_by_id(qcl_id) def get_method(self, method_id): return self.memDB.series_service.get_method_by_id(method_id) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 429bf2f..30ce67d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,27 +1,20 @@ import logging - - from sqlalchemy import distinct, func - - from odmtools.odmdata import SessionFactory from odmtools.odmdata import Site # from odmtools.odmdata import Variable from odm2api.ODM2.models import * -from odmtools.odmdata import Unit from odmtools.odmdata import Series from odmtools.odmdata import DataValue from odmtools.odmdata import Qualifier from odmtools.odmdata import OffsetType from odmtools.odmdata import Sample # from odmtools.odmdata import Method -from odm2api.ODM2.models import Methods from odmtools.odmdata import QualityControlLevel from odmtools.odmdata import ODMVersion from odmtools.common.logger import LoggerTool import pandas as pd from odm2api.ODM2.services.createService import CreateODM2 -from odm2api.ODM2.models import Annotations from odm2api.ODM2.services.readService import * @@ -51,12 +44,13 @@ def get_db_version(self): ##################### # Site methods - def get_all_sites(self): + def get_all_sites(self): # Site -> Sampling Feature in ODM2 """ :return: List[Sites] """ - return self._edit_session.query(Site).order_by(Site.code).all() + # return self._edit_session.query(Site).order_by(Site.code).all() + return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None,type=None, wkt=None) def get_used_sites(self): @@ -65,10 +59,12 @@ def get_used_sites(self): :return: List[Sites] """ try: - site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] + site_ids = [site_ids[0] for site_ids in self._edit_session.query(distinct(Results.FeatureActionID)).all()] except: site_ids = None + site_ids = self._edit_session.query(FeatureActions.SamplingFeatureID).filter(FeatureActions.FeatureActionID.in_(site_ids)).all() + if not site_ids: return None @@ -85,10 +81,11 @@ def get_site_by_id(self, site_id): :param site_id: integer- the identification number of the site :return: Sites """ - try: - return self._edit_session.query(Site).filter_by(id=site_id).first() - except: - return None + # try: + # return self._edit_session.query(Site).filter_by(id=site_id).first() + # except: + # return None + return self.read_service.getSamplingFeatures(ids=site_id) # Variables methods def get_used_variables(self): @@ -98,10 +95,12 @@ def get_used_variables(self): """ try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] + # var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] + var_ids = [x[0] for x in self._edit_session.query(distinct(Results.VariableID)).all()] except: var_ids = None + var_ids = self._edit_session.query(Results.VariableID).filter(Results.VariableID.in_(var_ids)).all() if not var_ids: return None @@ -115,10 +114,9 @@ def get_used_variables(self): def get_all_variables(self): """ - :return: List[Variables] """ - return self._edit_session.query(Variables).all() + return self.read_service.getVariables(ids=None, codes=None) def get_variable_by_id(self, variable_id): """ @@ -126,10 +124,7 @@ def get_variable_by_id(self, variable_id): :param variable_id: int :return: Variables """ - try: - return self._edit_session.query(Variables).filter_by(id=variable_id).first() - except: - return None + return self.read_service.getVariables(ids=variable_id) def get_variable_by_code(self, variable_code): """ @@ -137,10 +132,7 @@ def get_variable_by_code(self, variable_code): :param variable_code: str :return: Variables """ - try: - return self._edit_session.query(Variables).filter_by(code=variable_code).first() - except: - return None + return self.read_service.getVariables(codes=variable_code) def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits """ @@ -163,50 +155,39 @@ def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeU # Unit methods def get_all_units(self): """ - :return: List[Units] """ - return self._edit_session.query(Unit).all() + return self.read_service.getUnits(ids=None, name=None, type=None) def get_unit_by_name(self, unit_name): """ - :param unit_name: str :return: Units """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None + return self.read_service.getUnits(name=unit_name) def get_unit_by_id(self, unit_id): """ - :param unit_id: int :return: Units """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() - except: - return None + self.read_service.getUnits(ids=unit_id) - - def get_all_qualifiers(self): + def get_all_qualifiers(self): # Rename to annotations """ - :return: List[Qualifiers] """ # result = self._edit_session.query(Annotations).order_by(Annotations.AnnotationCode).all() # return result return self.read_service.getAnnotations(None) - def get_qualifier_by_code(self, code): + def get_qualifier_by_code(self, code): # Rename to get_annotations_by_type """ - :return: Qualifiers """ - result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - return result + # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + # return result + return self.read_service.getAnnotations(type=code) def get_qualifiers_by_series_id(self, series_id): """ @@ -218,40 +199,23 @@ def get_qualifiers_by_series_id(self, series_id): Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() return self._edit_session.query(Qualifier).join(subquery).distinct().all() - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() + def get_all_processing_levels(self): + return self.read_service.getProcessingLevels(ids=None, codes=None) - def get_qcl_by_id(self, qcl_id): - try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() - except: - return None + def get_processing_level_by_id(self, proc_level_id): + return self.read_service.getProcessingLevels(ids=proc_level_id) - def get_qcl_by_code(self, qcl_code): - try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() - except: - return None + def get_processing_level_by_code(self, proc_level_code): + return self.read_service.getProcessingLevels(codes=proc_level_code) - # Method methods def get_all_methods(self): - return self._edit_session.query(Methods).all() + return self.read_service.getMethods(ids=None, codes=None, type=None) def get_method_by_id(self, method_id): - try: - result = self._edit_session.query(Methods).filter_by(id=method_id).first() - except: - result = None - return result + return self.read_service.getMethods(ids=method_id) - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Methods).filter_by(description=method_code).first() - except: - result = None - logger.error("method not found") - return result + def get_method_by_code(self, method_code): + return self.read_service.getMethods(codes=method_code) def get_offset_types_by_series_id(self, series_id): """ diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 05efa88..d14c569 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -248,17 +248,17 @@ def test_get_data_value_by_id(self): assert dv.data_value == db_dv.data_value def test_get_qcl_by_id(self): - assert self.series_service.get_qcl_by_id(10) == None + assert self.series_service.get_processing_level_by_id(10) == None qcl = test_util.add_qcl(self.session) - db_qcl = self.series_service.get_qcl_by_id(qcl.id) + db_qcl = self.series_service.get_processing_level_by_id(qcl.id) assert qcl.code == db_qcl.code def test_get_all_qcls(self): - assert self.series_service.get_all_qcls() == [] + assert self.series_service.get_all_processing_levels() == [] qcl = test_util.add_qcl(self.session) - all_qcls = self.series_service.get_all_qcls() + all_qcls = self.series_service.get_all_processing_levels() assert len(all_qcls) == 1 assert qcl.id == all_qcls[0].id From ae5aed58cd5599a189a93d59dec638bdde923d34 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 17 Oct 2016 14:47:39 -0600 Subject: [PATCH 034/158] plot timeseries #293 --- odmtools/controller/frmSeriesSelector.py | 24 +- odmtools/controller/logicPlotOptions.py | 64 ++--- odmtools/gui/mnuRibbon.py | 2 +- odmtools/gui/plotBoxWhisker.py | 20 +- odmtools/gui/plotTimeSeries.py | 4 +- odmtools/odmservices/series_service.py | 302 +++++++++++------------ 6 files changed, 204 insertions(+), 212 deletions(-) diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index c306e7d..f83b008 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -162,7 +162,7 @@ def OnTableRightDown(self, event): # build pop-up menu for right-click display self.selectedIndex = event.m_itemIndex #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) - self.selectedID = self.tblSeries.GetSelectedObject().id + self.selectedID = self.tblSeries.GetSelectedObject().resultID # print self.selectedID popup_edit_series = wx.NewId() @@ -374,7 +374,7 @@ def siteAndVariables(self): :return: """ - self.site_code = self.siteList[self.cbSites.Selection].VariableCode + self.site_code = self.siteList[self.cbSites.Selection].SamplingFeatureCode self.cbVariables.Clear() self.varList = self.series_service.get_variables_by_site_code(self.site_code) @@ -400,7 +400,7 @@ def siteOnly(self): self.cbSites.Enabled = True self.variable_code = None - self.site_code = self.siteList[self.cbSites.Selection].code + self.site_code = self.siteList[self.cbSites.Selection].SamplingFeatureCode self.setFilter(site_code=self.site_code) def variableOnly(self): @@ -460,17 +460,17 @@ def setFilter(self, site_code='', var_code='', advfilter=''): :return: """ if site_code and var_code: - self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4],text=site_code) - self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7],text=var_code) + self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10],text=site_code) + self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10],text=var_code) self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) elif site_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:4], text=site_code)) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10], text=site_code)) elif var_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[6:7], text=var_code)) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10], text=var_code)) elif advfilter: self.tblSeries.SetFilter(advfilter) else: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:1])) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10])) self.tblSeries.RepopulateList() @@ -492,12 +492,12 @@ def onReadyToPlot(self, event): object = self.tblSeries.GetSelectedObject() if not self.tblSeries.IsChecked(object): - Publisher.sendMessage("removePlot", seriesID=object.id) + Publisher.sendMessage("removePlot", seriesID=object.ResultID) Publisher.sendMessage("updateCursor", deselectedObject=object) else: logger.debug("Obtained object, entering addplot") - self.pnlPlot.addPlot(self.memDB, object.id) + self.pnlPlot.addPlot(self.memDB, object.ResultID) Publisher.sendMessage("updateCursor", selectedObject=object) logger.debug("refreshing...") @@ -524,7 +524,7 @@ def getSelectedObject(self, event): ## update Cursor if self.parent.Parent.pnlPlot._seriesPlotInfo: - if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.id): + if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.ResultID): #print "Updating Cursor", editingObject.id Publisher.sendMessage("updateCursor", selectedObject=editingObject) @@ -563,7 +563,7 @@ def onReadyToEdit(self): ovl.RefreshObject(ovl.editingObject) - return True, object.id#, self.memDB + return True, object.ResultID#, self.memDB else: isSelected = False logger.debug("series was not checked") diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index cb521c7..dfa7d2c 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -54,7 +54,7 @@ def __init__(self, prnt): self.plotTitle = None self.numBins = 25 self.binWidth = 1.5 - self.boxWhiskerMethod = "Month" + self.boxWhiskerMethod = "month" self.yrange = 0 self.color = "" @@ -226,8 +226,10 @@ def getSelectedSeries(self, seriesID): return self.createSeriesInfo(seriesID, seriesInfo, series) def createSeriesInfo(self, seriesID, seriesInfo, series): - startDate = series.begin_date_time - endDate = series.end_date_time + + dates = self.memDB.series_service.get_result_dates(series.ResultID) + startDate = dates[1] + endDate = dates[0] if endDate > self.endDate: self.endDate = endDate @@ -237,14 +239,15 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): if not self.isSubsetted: self.currentStart = self.startDate self.currentEnd = self.endDate - - variableName = series.variable_name - unitsName = series.variable_units_name - siteName = series.site_name - dataType = series.data_type - variable = self.memDB.series_service.get_variable_by_id(series.variable_id) - - noDataValue = variable.no_data_value +#TODO odm2 + + unitsName = series.UnitsObj.UnitsName + siteName = series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureName + dataType = "datatype"#series.data_type + #variable = self.memDB.series_service.get_variable_by_id(series.variable_id) + variable =series.VariableObj + variableName = variable.VariableNameCV + noDataValue = variable.NoDataValue if self.editID == seriesID: #d= DataFrame(pandas.read_sql()) logger.debug("editing -- getting datavalues for graph") @@ -256,6 +259,7 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): data = self.memDB.getDataValuesforGraph(seriesID, noDataValue, self.currentStart, self.currentEnd) logger.debug("Finished plotting -- getting datavalues for graph") + logger.debug("assigning variables...") seriesInfo.seriesID = seriesID seriesInfo.series = series @@ -266,13 +270,13 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): seriesInfo.siteName = siteName seriesInfo.variableName = variableName seriesInfo.variableUnits = unitsName - seriesInfo.plotTitle = "Site: " + siteName + "\nVarName: " + variableName + "\nQCL: " + series.quality_control_level_code - seriesInfo.axisTitle = variableName + " (" + unitsName + ")" + seriesInfo.plotTitle = "Site: %s \nVarName: %s \nQCL: %s" %(siteName, variableName, series.ProcessingLevelID) + seriesInfo.axisTitle = "%s (%s)"%(variableName, unitsName) seriesInfo.noDataValue = noDataValue seriesInfo.dataTable = data if len(data) > 0: - seriesInfo.yrange = np.max(data['DataValue']) - np.min(data['DataValue']) + seriesInfo.yrange = np.max(data['datavalue']) - np.min(data['datavalue']) else: seriesInfo.yrange = 0 @@ -298,9 +302,9 @@ def getSeriesInfo(self, seriesID): def buildPlotInfo(self, seriesInfo): #remove all of the nodatavalues from the pandas table - filteredData = seriesInfo.dataTable[seriesInfo.dataTable["DataValue"] != seriesInfo.noDataValue] - val = filteredData["Month"].map(calcSeason) - filteredData["Season"] = val + filteredData = seriesInfo.dataTable[seriesInfo.dataTable["datavalue"] != seriesInfo.noDataValue] + val = filteredData["month"].map(calcSeason) + filteredData["season"] = val # construct tasks for the task server tasks = [("Probability", filteredData), @@ -353,12 +357,12 @@ class Statistics(object): def __init__(self, data): start_time = timeit.default_timer() - dvs = data["DataValue"] + dvs = data["datavalue"] count = len(dvs) if count > 0: time = timeit.default_timer() - self.NumberofCensoredObservations = len(data[data["CensorCode"] != "nc"]) + self.NumberofCensoredObservations = len(data[data["censorcodecv"] != "nc"]) elapsed = timeit.default_timer() - time logger.debug("censored observations using len: %s" % elapsed) @@ -396,13 +400,13 @@ def __init__(self, data, method): self.intervals = {} self.method = method - interval_types = ["Overall", "Year", "Month", "Season"] - intervals = ["Overall", "Year", "Month", "Season"] + interval_types = ["overall", "year", "month", "season"] + intervals = ["overall", "year", "month", "season"] interval_options = zip(interval_types, intervals) for interval_type, interval in interval_options: start_time = timeit.default_timer() - if interval_type == "Overall": + if interval_type == "overall": interval = data else: interval = data.groupby(interval_type) @@ -421,18 +425,18 @@ def calculateBoxWhiskerData(self, interval, interval_type): results = self.calculateIntervalsOnGroups(interval) - if interval_type == "Season" or interval_type == "Month": + if interval_type == "season" or interval_type == "month": func = None - if interval_type == "Season": + if interval_type == "season": func = numToSeason - elif interval_type == "Month": + elif interval_type == "month": func = numToMonth self.intervals[interval_type] = BoxWhiskerPlotInfo( interval_type, interval_type, [func(x) for x in results["names"]], [results["median"], results["conflimit"], results["mean"], results["confint"]]) - elif interval_type == "Overall": + elif interval_type == "overall": self.intervals[interval_type] = BoxWhiskerPlotInfo( interval_type, None, [], [results["median"], results["conflimit"], results["mean"], results["confint"]]) @@ -452,7 +456,7 @@ def calculateIntervalsOnGroups(self, interval): if isinstance(interval, pd.core.groupby.DataFrameGroupBy): for name, group in interval: - datavalue = group['DataValue'] + datavalue = group['datavalue'] group_mean = np.mean(datavalue) group_median = np.median(datavalue) group_std = math.sqrt(np.var(datavalue)) @@ -467,8 +471,8 @@ def calculateIntervalsOnGroups(self, interval): median.append(group_median) mean.append(group_mean) else: - name = "Overall" - datavalue = interval['DataValue'] + name = "overall" + datavalue = interval['datavalue'] data_mean = np.mean(datavalue) data_median = np.median(datavalue) data_std = math.sqrt(np.var(datavalue)) @@ -539,7 +543,7 @@ def __init__(self, data): :param data: :return: """ - self.yAxis = data['DataValue'] + self.yAxis = data['datavalue'] # Determine rank, sorting values doesn't change outcome while using pandas. ranks = self.yAxis.rank() PrbExc = ranks / (len(ranks) + 1) * 100 diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index 360f6cc..389c1ce 100755 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -658,7 +658,7 @@ def enableButtons(self, plot, isActive): self.spnBins.Enabled = False self.enableDateSelection(True) - ##HIstogram + ##Histogram elif plot == 2: self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSTYPE, False) self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSLEGEND, False) diff --git a/odmtools/gui/plotBoxWhisker.py b/odmtools/gui/plotBoxWhisker.py index ea6ef84..3058d7d 100644 --- a/odmtools/gui/plotBoxWhisker.py +++ b/odmtools/gui/plotBoxWhisker.py @@ -116,17 +116,17 @@ def _createPlot(self, oneSeries, rows, cols, index): ax.scatter([range(1, len(med) + 1)], med, marker='s', c="k", s=10) # bp = onSeries.dataTable.boxplot( - bp = oneSeries.dataTable[oneSeries.dataTable["DataValue"]<>oneSeries.noDataValue].boxplot(column="DataValue", ax=ax, by=oneSeries.BoxWhisker.currinterval.groupby, + bp = oneSeries.dataTable[oneSeries.dataTable["datavalue"]<>oneSeries.noDataValue].boxplot(column="datavalue", ax=ax, by=oneSeries.BoxWhisker.currinterval.groupby, rot=35, notch=True, sym="-s", conf_intervals=ci, return_type='dict', grid=False) # Set Colors of the Box Whisker plot try: - plt.setp(bp['DataValue']['whiskers'], color='k', linestyle='-') - plt.setp(bp['DataValue']['medians'], color='k', linestyle='-') - plt.setp(bp['DataValue']['boxes'], color='GREY', linestyle='-') - plt.setp(bp['DataValue']['caps'], color='k') - plt.setp(bp['DataValue']['fliers'], markersize=3.5, color=oneSeries.color) + plt.setp(bp['datavalue']['whiskers'], color='k', linestyle='-') + plt.setp(bp['datavalue']['medians'], color='k', linestyle='-') + plt.setp(bp['datavalue']['boxes'], color='GREY', linestyle='-') + plt.setp(bp['datavalue']['caps'], color='k') + plt.setp(bp['datavalue']['fliers'], markersize=3.5, color=oneSeries.color) except: plt.setp(bp['whiskers'], color='k', linestyle='-') plt.setp(bp['medians'], color='k', linestyle='-') @@ -169,22 +169,22 @@ def setColor(self, color): def monthly(self, str): # print "monthly" - self.seriesPlotInfo.setBoxInterval("Month") + self.seriesPlotInfo.setBoxInterval("month") self.updatePlot() def seasonaly(self, str): # print"seasonal" - self.seriesPlotInfo.setBoxInterval("Season") + self.seriesPlotInfo.setBoxInterval("season") self.updatePlot() def yearly(self, str): # print "yearly" - self.seriesPlotInfo.setBoxInterval("Year") + self.seriesPlotInfo.setBoxInterval("year") self.updatePlot() def overall(self, str): # print "overall" - self.seriesPlotInfo.setBoxInterval("Overall") + self.seriesPlotInfo.setBoxInterval("overall") self.updatePlot() diff --git a/odmtools/gui/plotTimeSeries.py b/odmtools/gui/plotTimeSeries.py index d5026b1..2fbfd20 100755 --- a/odmtools/gui/plotTimeSeries.py +++ b/odmtools/gui/plotTimeSeries.py @@ -412,7 +412,7 @@ def updatePlot(self): data = oneSeries.dataTable dates = data.index.astype(datetime.datetime) #data.plot(ax=curraxis) - curraxis.plot_date(dates, data['DataValue'], + curraxis.plot_date(dates, data['datavalue'], color=oneSeries.color, fmt=self.format, xdate=True, tz=None, antialiased=True, label=oneSeries.plotTitle, alpha=self.alpha, picker=5.0, pickradius=5.0, markersize=4) @@ -484,7 +484,7 @@ def updateCursor(self, selectedObject=None, deselectedObject=None): Activate Cursor. Happens when a plot is selected """ if self.seriesPlotInfo: - seriesInfo = self.seriesPlotInfo.getSeries(selectedObject.id) + seriesInfo = self.seriesPlotInfo.getSeries(selectedObject.ResultID) if seriesInfo: currentAxis = None diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 34b3e17..a91dec1 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -43,6 +43,10 @@ def reset_session(self): def get_used_sites(self): + """ + Return a list of all sites that are being referenced in the Series Catalog Table + :return: List[Sites] + """ try: fas=[x[0] for x in self._session.query(distinct(Results.FeatureActionID)).all()] except: @@ -54,6 +58,10 @@ def get_used_sites(self): return sites def get_used_variables(self): + """ + #get list of used variable ids + :return: List[Variables] + """ try: ids= [x[0] for x in self._session.query(distinct(Results.VariableID)).all()] except: @@ -63,9 +71,36 @@ def get_used_variables(self): return vars + + # Query DetailedResultInfo/series object is for Display purposes + def get_all_series(self): + """ + Returns all series as a modelObject + :return: List[Series] + """ return self.read.getDetailedResultInfo('Time Series Coverage') + def get_series_by_id(self, series_id): + """ + + :param series_id: int + :return: Series + """ + # try: + # return self.read.getDetailedResultInfo('Time Series Coverage', resultID = series_id)[0] + # except Exception as e: + # print e + # return None + return self.read.getResults(ids=[series_id])[0] + + # Query result objects for data purposes + def get_result_dates(self, result_id): + q = self.read._session.query( + func.max(TimeSeriesResultValues.ValueDateTime), func.min(TimeSeriesResultValues.ValueDateTime) + ).filter(TimeSeriesResultValues.ResultID == result_id) + return q.all()[0] + def get_variables_by_site_code(self, site_code): """ Finds all of variables at a site @@ -74,44 +109,21 @@ def get_variables_by_site_code(self, site_code): """ try: var_ids = [x[0] for x in - self._session.query(distinct(Results.VariableID)) \ - .filter(Results.FeatureActionID == FeatureActions.FeatureActionID) \ - .filter(FeatureActions.SamplingFeatureID == SamplingFeatures.SamplingFeatureID) \ - .filter(SamplingFeatures.SamplingFeatureCode == site_code) + self._session.query(distinct(Results.VariableID)) + .filter(Results.FeatureActionID == FeatureActions.FeatureActionID) + .filter(FeatureActions.SamplingFeatureID == SamplingFeatures.SamplingFeatureID) + .filter(SamplingFeatures.SamplingFeatureCode == site_code).all() ] except: var_ids = None + q = self._session.query(Variables).filter(Variables.VariableID.in_(var_ids)) + return q.all() -# def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits -# """ -# Finds all of variables at a site -# :param site_code: str -# :return: List[Variables] -# """ -# try: -# var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( -# site_code=site_code).all()] -# except: -# var_ids = None -# -# variables = [] -# for var_id in var_ids: -# variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) -# -# return variables - # Series Catalog methods -# def get_all_series(self): -# """ -# Returns all series as a modelObject -# :return: List[Series] -# """ -# -# #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) -# return self._edit_session.query(Series).order_by(Series.id).all() + # # def get_series_by_site(self , site_id): # """ @@ -124,18 +136,7 @@ def get_variables_by_site_code(self, site_code): # return selectedSeries # except: # return None -# -# def get_series_by_id(self, series_id): -# """ -# -# :param series_id: int -# :return: Series -# """ -# try: -# return self._edit_session.query(Series).filter_by(id=series_id).first() -# except Exception as e: -# print e -# return None + # @@ -148,60 +149,22 @@ def get_variables_by_site_code(self, site_code): # return self._edit_session.query(Site).order_by(Site.code).all() # # -# def get_used_sites(self): -# """ -# Return a list of all sites that are being referenced in the Series Catalog Table -# :return: List[Sites] -# """ -# try: -# site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] -# except: -# site_ids = None # -# if not site_ids: -# return None -# -# Sites = [] -# for site_id in site_ids: -# Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) -# -# return Sites - -# -# -# def get_site_by_id(self, site_id): -# """ -# return a Site object that has an id=site_id -# :param site_id: integer- the identification number of the site -# :return: Sites -# """ + def get_site_by_id(self, site_id): + """ + return a Site object that has an id=site_id + :param site_id: integer- the identification number of the site + :return: Sites + """ # try: # return self._edit_session.query(Site).filter_by(id=site_id).first() # except: # return None + + return self.read.getSampling(ids = [site_id])[0] + # -# # Variables methods -# def get_used_variables(self): -# """ -# #get list of used variable ids -# :return: List[Variables] -# """ -# -# try: -# var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] -# except: -# var_ids = None -# -# if not var_ids: -# return None -# -# Variables = [] -# -# #create list of variables from the list of ids -# for var_id in var_ids: -# Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) -# -# return Variables + # # def get_all_variables(self): # """ @@ -210,16 +173,17 @@ def get_variables_by_site_code(self, site_code): # """ # return self._edit_session.query(Variable).all() # -# def get_variable_by_id(self, variable_id): -# """ -# -# :param variable_id: int -# :return: Variables -# """ + def get_variable_by_id(self, variable_id): + """ + + :param variable_id: int + :return: Variables + """ # try: # return self._edit_session.query(Variable).filter_by(id=variable_id).first() # except: # return None + return self.read.getVariables(ids = [variable_id])[0] # # def get_variable_by_code(self, variable_code): # """ @@ -402,30 +366,38 @@ def get_variables_by_site_code(self, site_code): # pass # # -# #Data Value Methods -# def get_values_by_series(self, series_id): -# ''' -# -# :param series_id: Series id -# :return: pandas dataframe -# ''' -# series= self.get_series_by_id(series_id) -# if series: -# q = self._edit_session.query(DataValue).filter_by( -# site_id=series.site_id, -# variable_id=series.variable_id, -# method_id=series.method_id, -# source_id=series.source_id, -# quality_control_level_id=series.quality_control_level_id) -# -# query=q.statement.compile(dialect=self._session_factory.engine.dialect) -# data= pd.read_sql_query(sql= query, -# con = self._session_factory.engine, -# params = query.params ) -# #return data.set_index(data['LocalDateTime']) -# return data -# else: -# return None + #Data Value Methods + def get_values_by_series(self, series_id): + ''' + + :param series_id: Series id + :return: pandas dataframe + ''' + #series= self.get_series_by_id(series_id) + # if series: + # q = self._edit_session.query(DataValue).filter_by( + # site_id=series.site_id, + # variable_id=series.variable_id, + # method_id=series.method_id, + # source_id=series.source_id, + # quality_control_level_id=series.quality_control_level_id) + # + # query=q.statement.compile(dialect=self._session_factory.engine.dialect) + # data= pd.read_sql_query(sql= query, + # con = self._session_factory.engine, + # params = query.params ) + # #return data.set_index(data['LocalDateTime']) + # return data + # else: + # return None + + q = self.read._session.query(TimeSeriesResultValues).filter_by(ResultID=series_id).order_by(TimeSeriesResultValues.ValueDateTime) + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, + con=self._session_factory.engine, + params=query.params) + data.set_index(data['valuedatetime'], inplace=True) + return data # # def get_all_values_df(self): # """ @@ -456,19 +428,20 @@ def get_variables_by_site_code(self, site_code): # def get_all_values(self): # return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() # -# @staticmethod -# def calcSeason(row): -# -# month = int(row["Month"]) -# -# if month in [1, 2, 3]: -# return 1 -# elif month in[4, 5, 6]: -# return 2 -# elif month in [7, 8, 9]: -# return 3 -# elif month in [10, 11, 12]: -# return 4 + @staticmethod + def calcSeason(row): + + month = int(row["month"]) + + if month in [1, 2, 3]: + return 1 + elif month in[4, 5, 6]: + return 2 + elif month in [7, 8, 9]: + return 3 + elif month in [10, 11, 12]: + return 4 + # # def get_all_plot_values(self): # """ @@ -490,30 +463,45 @@ def get_variables_by_site_code(self, site_code): # data["Season"] = data.apply(self.calcSeason, axis=1) # return data.set_index(data['LocalDateTime']) # -# def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): -# """ -# -# :param seriesID: -# :param noDataValue: -# :param startDate: -# :param endDate: -# :return: -# """ -# series = self.get_series_by_id(seriesID) -# -# DataValues = [ -# (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), -# dv.local_date_time.strftime('%Y')) -# for dv in series.data_values -# if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate -# ] -# data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) -# data.set_index(data['LocalDateTime'], inplace=True) -# data["Season"] = data.apply(self.calcSeason, axis=1) -# return data -# -# -# + def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): + """ + + :param seriesID: + :param noDataValue: + :param startDate: + :param endDate: + :return: + """ + + #series = self.get_series_by_id(seriesID) + # + # DataValues = [ + # (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), + # dv.local_date_time.strftime('%Y')) + # for dv in series.data_values + # if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate + # ] + # data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) + # data.set_index(data['LocalDateTime'], inplace=True) + # data["Season"] = data.apply(self.calcSeason, axis=1) + # return data + + + Values = self.get_values_by_series(seriesID) + data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] + + + #data.set_index(data['LocalDateTime'], inplace=True) + data["month"] = data['valuedatetime'].apply(lambda x: x.month) + data["year"] = data['valuedatetime'].apply(lambda x: x.year) + data["season"] = data.apply(self.calcSeason, axis=1) + return data + + + + + + # def get_data_value_by_id(self, id): # """ # From 3b914f9757fa4db115f5b2e4925017ed5e2a246f Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 17 Oct 2016 15:00:06 -0600 Subject: [PATCH 035/158] rename all datavalue pandas columns --- odmtools/controller/olvSeriesSelector.py | 2 +- odmtools/gui/mnuPlotToolbar.py | 2 +- odmtools/gui/plotHistogram.py | 2 +- odmtools/odmservices/edit_service.py | 24 ++++++++++++------------ tests/test_odmdata/test_memory_db.py | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index cf1de92..a3a107d 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -67,7 +67,7 @@ def _buildColumns(self, columns): seriesColumns = [ ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, # stringConverter = '%s') - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') + stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else '%s') for key in columns] diff --git a/odmtools/gui/mnuPlotToolbar.py b/odmtools/gui/mnuPlotToolbar.py index d969857..167900d 100644 --- a/odmtools/gui/mnuPlotToolbar.py +++ b/odmtools/gui/mnuPlotToolbar.py @@ -271,7 +271,7 @@ def on_toggle_zoom_data_tool(self, event): #date= [x[1] for x in self.editCurve.dataTable if x[0] != self.editCurve.noDataValue] axes = self.canvas.figure.axes[0] - axes.set_ylim(min(nodvvals["DataValue"]), max(nodvvals["DataValue"])) + axes.set_ylim(min(nodvvals["datavalue"]), max(nodvvals["datavalue"])) axes.set_xlim(dates.date2num([min(nodvvals.index), max(nodvvals.index)])) self.push_current() diff --git a/odmtools/gui/plotHistogram.py b/odmtools/gui/plotHistogram.py index a3a8231..6070da7 100644 --- a/odmtools/gui/plotHistogram.py +++ b/odmtools/gui/plotHistogram.py @@ -105,7 +105,7 @@ def _createPlot(self, oneSeries, rows, cols, index): # oneSeries.filteredData.hist(ax= ax, color='k', alpha=0.5, bins=50) - his = oneSeries.dataTable.hist(column="DataValue", ax=ax, bins=self.bins, + his = oneSeries.dataTable.hist(column="datavalue", ax=ax, bins=self.bins, facecolor=oneSeries.color, label=oneSeries.siteName + " " + oneSeries.variableName, grid=False) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index d3448fd..bb7dab2 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -98,9 +98,9 @@ def datetime2dataframe(self, datetime_list): if isinstance(datetime_list, list): - result = pd.DataFrame(datetime_list, columns=["LocalDateTime"]) + result = pd.DataFrame(datetime_list, columns=["valuedatetime"]) - result.set_index("LocalDateTime", inplace=True) + result.set_index("valuedatetime", inplace=True) return result @@ -130,10 +130,10 @@ def filter_value(self, value, ops): df = self._test_filter_previous() if ops == '>': - self.filtered_dataframe = df[df['DataValue'] > value] + self.filtered_dataframe = df[df['datavalue'] > value] if ops == '<': - self.filtered_dataframe = df[df['DataValue'] < value] + self.filtered_dataframe = df[df['datavalue'] < value] def filter_date(self, before, after): @@ -185,9 +185,9 @@ def change_value_threshold(self, value, operator): # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps copy_df = df - copy_df['values'] = df['DataValue'] + copy_df['values'] = df['datavalue'] copy_df['diff'] = copy_df['values'].shift() - copy_df["diff_date"] = copy_df['LocalDateTime'].shift() + copy_df["diff_date"] = copy_df['valuedatetime'].shift() copy_df['change_threshold'] = abs(df['values'] - df['diff']) if not isinstance(value, float): @@ -337,13 +337,13 @@ def interpolate(self): df = self._series_points_df issel = df.index.isin(tmp_filter_list.index) - mdf = df["DataValue"].mask(issel) + mdf = df["datavalue"].mask(issel) mdf.interpolate(method = "time", inplace=True) - tmp_filter_list["DataValue"]=mdf[issel] + tmp_filter_list["datavalue"]=mdf[issel] ids = tmp_filter_list.index.tolist() #update_list = [(row["DataValue"], row["ValueID"]) for index, row in tmp_filter_list.iterrows()] - update_list = [{"value": row["DataValue"], "id": index} for index, row in tmp_filter_list.iterrows()] + update_list = [{"value": row["datavalue"], "id": index} for index, row in tmp_filter_list.iterrows()] self.memDB.update(update_list) @@ -359,10 +359,10 @@ def drift_correction(self, gap_width): x_l = (tmp_filter_list.index[-1]-startdate).total_seconds() # y_n = y_0 + G(x_i / x_l) - f = lambda row : row["DataValue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) - tmp_filter_list["DataValue"]=tmp_filter_list.apply(f, axis = 1) + f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) + tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) - update_list = [{"value": row["DataValue"], "id":index} for index, row in tmp_filter_list.iterrows()] + update_list = [{"value": row["datavalue"], "id":index} for index, row in tmp_filter_list.iterrows()] ids = tmp_filter_list.index.tolist() self.memDB.update(update_list) diff --git a/tests/test_odmdata/test_memory_db.py b/tests/test_odmdata/test_memory_db.py index d3b6cb3..090975b 100644 --- a/tests/test_odmdata/test_memory_db.py +++ b/tests/test_odmdata/test_memory_db.py @@ -37,13 +37,13 @@ def test_update_points(self): self.memory_db.update([{"value":15,"id":self.sdate}]) dvs = self.memory_db.getDataValuesDF() - print dvs["DataValue"] - assert dvs["DataValue"][0] == 15 + print dvs["datavalue"] + assert dvs["datavalue"][0] == 15 def test_update_value(self): self.memory_db.updateValue([self.sdate],'+', 5 ) dvs = self.memory_db.getDataValuesDF() - assert dvs["DataValue"][0] == 14 + assert dvs["datavalue"][0] == 14 def test_add_points(self): #with pytest.raises(NotImplementedError): @@ -55,7 +55,7 @@ def test_add_points(self): dvs = self.memory_db.getDataValuesDF() assert len(dvs.index) == 11 - assert dvs["DataValue"][0] == -9999 + assert dvs["datavalue"][0] == -9999 def test_update_flag(self): self.memory_db.updateFlag([self.sdate], '50') @@ -66,7 +66,7 @@ def test_update_flag(self): def test_delete_points(self): stlen= len(self.memory_db.df.index) - self.memory_db.delete(self.memory_db.df["LocalDateTime"].tolist()[0:10]) + self.memory_db.delete(self.memory_db.df["resultdatetime"].tolist()[0:10]) dvs = self.memory_db.getDataValuesDF() assert len(dvs.index) == stlen-10 From fc165376132645adf032eaed2de84958ab499e6c Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 17 Oct 2016 17:15:32 -0600 Subject: [PATCH 036/158] fix date time plot filter #293 --- odmtools/gui/plotProbability.py | 4 ++-- odmtools/odmservices/series_service.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 6e23761..f758c86 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -136,8 +136,8 @@ def updatePlot(self): #self.prob.append( #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) #todo FutureWarning: order is deprecated, use sort_values(...) - xValues = oneSeries.Probability.xAxis.order().values - yValues = oneSeries.Probability.yAxis.order().values + xValues = oneSeries.Probability.xAxis.sort_values().values + yValues = oneSeries.Probability.yAxis.sort_values().values ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index a91dec1..cb96310 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -489,6 +489,9 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non Values = self.get_values_by_series(seriesID) data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] + # data = data[data['datavalue'] != noDataValue] + data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( + data['valuedatetime'] <= endDate)] #data.set_index(data['LocalDateTime'], inplace=True) From a185c9e22586410112afde689f37f016b7cad846 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 17 Oct 2016 21:33:12 -0600 Subject: [PATCH 037/158] update get_values --- odmtools/common/taskServer.py | 2 +- odmtools/odmdata/memory_database.py | 4 ++-- odmtools/odmservices/series_service.py | 29 ++++++++++++++++++-------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index c38f707..0e78e0d 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -137,7 +137,7 @@ def worker(cls, dispatcher): result = connection if task_type == "UpdateEditDF": connection = task[1] - result = connection.get_all_values_df() + result = connection.get_values() result = (task_type, result) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 06ec67a..176f6ae 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -200,7 +200,7 @@ def updateDF(self): self.taskserver.processTasks() else: ''' - self.df = self.mem_service.get_all_values_df() + self.df = self.mem_service.get_values() print self.mem_service._version @@ -212,7 +212,7 @@ def initEditValues(self, seriesID): if not self.editLoaded: logger.debug("Load series from db") - self.df = self.series_service.get_values_by_series(seriesID) + self.df = self.series_service.get_values(seriesID) self.editLoaded = True ''' diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index cb96310..83f40a7 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -367,7 +367,7 @@ def get_variable_by_id(self, variable_id): # # #Data Value Methods - def get_values_by_series(self, series_id): + def get_values(self, series_id=None): ''' :param series_id: Series id @@ -391,19 +391,23 @@ def get_values_by_series(self, series_id): # else: # return None - q = self.read._session.query(TimeSeriesResultValues).filter_by(ResultID=series_id).order_by(TimeSeriesResultValues.ValueDateTime) + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q=q.filter_by(ResultID=series_id) + q= q.order_by(TimeSeriesResultValues.ValueDateTime) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, params=query.params) data.set_index(data['valuedatetime'], inplace=True) return data -# -# def get_all_values_df(self): -# """ -# -# :return: Pandas DataFrame object -# """ + + # def get_all_values_df(self): + + # """ + # + # :return: Pandas DataFrame object + # """ # q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) # query = q.statement.compile(dialect=self._session_factory.engine.dialect) # data = pd.read_sql_query(sql=query, con=self._session_factory.engine, @@ -416,6 +420,13 @@ def get_values_by_series(self, series_id): # # data = data.ix[:, columns] # return data.set_index(data['LocalDateTime']) +# q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) +# query = q.statement.compile(dialect = self._session_factory.engine.dialect) +# data = pd.read_sql_query(sql= query, +# con= self._session_factory.engine, +# params=query.params) +# + # # def get_all_values_list(self): # """ @@ -487,7 +498,7 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non # return data - Values = self.get_values_by_series(seriesID) + Values = self.get_values(seriesID) data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] # data = data[data['datavalue'] != noDataValue] data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( From 6794dba71fcdde5497efb679cd1355667b0d295c Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 20 Oct 2016 12:08:39 -0600 Subject: [PATCH 038/158] #293 Upadated test_util.py --- tests/test_odmservices/test_series_service.py | 8 +- tests/test_util.py | 140 ++++++++---------- 2 files changed, 62 insertions(+), 86 deletions(-) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index d14c569..9abaf1e 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -250,14 +250,14 @@ def test_get_data_value_by_id(self): def test_get_qcl_by_id(self): assert self.series_service.get_processing_level_by_id(10) == None - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) db_qcl = self.series_service.get_processing_level_by_id(qcl.id) assert qcl.code == db_qcl.code def test_get_all_qcls(self): assert self.series_service.get_all_processing_levels() == [] - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) all_qcls = self.series_service.get_all_processing_levels() assert len(all_qcls) == 1 @@ -307,7 +307,7 @@ def test_create_new_series(self): variable = test_util.add_variable(self.session) method = test_util.add_method(self.session) source = test_util.add_source(self.session) - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) dvs = [] for val in range(10): @@ -382,7 +382,7 @@ def test_delete_values(self): def test_qcl_exists(self): - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) assert self.series_service.qcl_exists(qcl) == True qcl.code = "00000" diff --git a/tests/test_util.py b/tests/test_util.py index 444fb7a..77dfdd8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -3,6 +3,8 @@ from odmtools.odmdata import * import os import sys +from odm2api.ODM2.models import * +from odm2api.ODM2.services.readService import DetailedResult def build_db(engine): @@ -40,10 +42,11 @@ def add_bulk_data_values(session, series, dvs_size): session.commit() return df + def add_series_bulk_data(session, dvs_size=50): site = add_site(session) var = add_variable(session) - qcl = add_qcl(session) + qcl = add_process_level(session) method = add_method(session) source = add_source(session) @@ -73,33 +76,18 @@ def add_series_bulk_data(session, dvs_size=50): session.commit() return series + # Create Series objects def add_series(session): - site = add_site(session) + result = Results() var = add_variable(session) - qcl = add_qcl(session) - method = add_method(session) - source = add_source(session) - - series = Series() - series.site = site - series.site_code = site.code - series.variable = var - series.variable_code = var.code - series.method = method - series.source = source - series.quality_control_level_id = qcl.id - - dvs = add_data_values(session, series) - series.begin_date_time = dvs[0].local_date_time - series.end_date_time = dvs[-1].local_date_time - series.begin_date_time_utc = dvs[0].date_time_utc - series.end_date_time_utc = dvs[-1].date_time_utc - series.value_count = len(dvs) - - session.add(series) + qcl = add_process_level(session) + result.VariableObj = var + result.ProcessingLevelObj = qcl + result.ProcessingLevelID = qcl.ProcessingLevelID + session.add(result) session.commit() - return series + return result def add_data_values(session, series): @@ -126,14 +114,14 @@ def add_data_values(session, series): def add_site(session): spatial_ref = add_spatial_reference(session) - site = Site("ABC123", "Test Site") - site.latitude = 10.0 - site.longitude = 10.0 - site.lat_long_datum_id = spatial_ref.id - site.local_projection_id = spatial_ref.id - site.elevation_m = 1000 - site.local_x = 10.0 - site.local_y = 10.0 + site = Sites("ABC123", "Test Site") + site.Latitude = 10.0 + site.Longitude = 10.0 + site.lat_long_datum_id = spatial_ref.SpatialReferenceID + site.local_projection_id = spatial_ref.SpatialReferenceID + site.Elevation_m = 1000 + # site.local_x = 10.0 + # site.local_y = 10.0 session.add(site) session.commit() return site @@ -141,60 +129,48 @@ def add_site(session): def add_variable(session): unit = add_unit(session) - variable = Variable() - variable.code = "ABC123" - variable.name = "Test Variable" - variable.speciation = "Test" - variable.variable_unit_id = unit.id - variable.sample_medium = "Test Medium" - variable.value_type = "Test Val Type" - variable.is_regular = True - variable.time_support = 3.14 - variable.time_unit_id = unit.id - variable.data_type = "Test Data Type" - variable.general_category = "Test Category" - variable.no_data_value = -2000.0 + variable = Variables() + variable.VariableCode = "ABC123" + variable.VariableNameCV = "Test Variable" + variable.SpeciationCV = "Test" + variable.VariableID = unit.id + variable.NoDataValue = -2000.0 session.add(variable) session.commit() return variable def add_method(session): - method = Method() - method.description = "This is a test" + method = Methods() + method.MethodDescription = "This is a test" session.add(method) session.commit() return method -def add_qcl(session): - qcl = QualityControlLevel() - qcl.code = "ABC123" - qcl.definition = "This is a test" - qcl.explanation = "A test is a thing that tests code" - session.add(qcl) +def add_process_level(session): + proc_level = ProcessingLevels() + proc_level.ProcessingLevelCode = "ABC123" + proc_level.Definition = "This is a test" + proc_level.Explanation = "A test is a thing that tests code" + session.add(proc_level) session.commit() - return qcl + return proc_level def add_source(session): - source = Source() - source.organization = "Test Organization" - source.description = "This is a test" - source.contact_name = "Test Name" - source.phone = "555-1234" - source.email = "source@example.com" - source.address = "123 Test Street" - source.city = "Metropolis" - source.state = "NY" - source.zip_code = "12345" - source.citation = "Test Citation" - - iso = add_iso_metadata(session) - source.iso_metadata_id = iso.id - session.add(source) + organization = Organizations() + affiliation = Affiliations() + organization.OrganizationName = "Test Organization" + organization.OrganizationDescription = "This is a test" + affiliation.PersonLink = "Test Name" + affiliation.PrimaryPhone = "555-1234" + affiliation.PrimaryEmail = "source@example.com" + affiliation.PrimaryAddress = "123 Test Street" + affiliation.OrganizationObj = organization + session.add(affiliation) session.commit() - return source + return affiliation def add_iso_metadata(session): @@ -209,8 +185,8 @@ def add_iso_metadata(session): def add_spatial_reference(session): - spatial_ref = SpatialReference() - spatial_ref.srs_name = "This is a test" + spatial_ref = SpatialReferences() + spatial_ref.SRSName = "This is a test" session.add(spatial_ref) session.commit() return spatial_ref @@ -258,19 +234,19 @@ def add_site_type_cv(session): def add_variable_name_cv(session): - var_name_cv = VariableNameCV() - var_name_cv.term = "Test" - var_name_cv.definition = "This is a test" + var_name_cv = CVVariableName() + var_name_cv.Term = "Test" + var_name_cv.Definition = "This is a test" session.add(var_name_cv) session.commit() return var_name_cv def add_unit(session): - unit = Unit() - unit.name = "Test" - unit.type = "Test" - unit.abbreviation = "T" + unit = Units() + unit.UnitsName = "Test" + unit.UnitsTypeCV = "Test" + unit.UnitsAbbreviation = "T" session.add(unit) session.commit() return unit @@ -286,9 +262,9 @@ def add_offset_type_cv(session, unit_id): def add_speciation_cv(session): - spec = SpeciationCV() - spec.term = "Test" - spec.definition = "This is a test" + spec = CVSpeciation() + spec.Term = "Test" + spec.Definition = "This is a test" session.add(spec) session.commit() return spec From 70eac3c6a5cdb16f51b9c1f5f996be262acdc2d9 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 20 Oct 2016 12:47:30 -0600 Subject: [PATCH 039/158] #293 Finished updating test_util.py --- tests/test_util.py | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 77dfdd8..6827b9f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -25,18 +25,13 @@ def add_bulk_data_values(session, series, dvs_size): df['DateTimeUTC'] = pd.to_datetime(df['DateTimeUTC']).astype(datetime.datetime) dvs = [] for record in df.to_dict('records')[:dvs_size]: - dv = DataValue() - dv.data_value = record['DataValue'] - dv.local_date_time = record['LocalDateTime'] - dv.utc_offset = record['UTCOffset'] - dv.date_time_utc = record['DateTimeUTC'] - dv.site_id = series.site_id - dv.variable_id = series.variable_id - dv.censor_code = record['CensorCode'] - dv.method_id = series.method_id - dv.source_id = series.source_id - dv.quality_control_level_id = series.quality_control_level_id - dvs.append(dv) + timeseries_result_value = TimeSeriesResultValues() + timeseries_result_value.DataValue = record["DataValue"] + timeseries_result_value.ValueDateTimeUTCOffset = record["UTCOffset"] + timeseries_result_value.ValueDateTime = record["DateTimeUTC"] + timeseries_result_value.CensorCodeCV = record["CensorCode"] + timeseries_result_value.QualityCodeCV = series.quality_control_level_code + dvs.append(timeseries_result_value) series.data_values = dvs session.add_all(dvs) session.commit() @@ -93,18 +88,14 @@ def add_series(session): def add_data_values(session, series): dvs = [] for i in range(10): - dv = DataValue() - dv.data_value = i - dv.local_date_time = datetime.datetime.now() - datetime.timedelta(days=i) - dv.utc_offset = 0 - dv.date_time_utc = dv.local_date_time - dv.site_id = series.site_id - dv.variable_id = series.variable_id - dv.censor_code = "NC" - dv.method_id = series.method_id - dv.source_id = series.source_id - dv.quality_control_level_id = series.quality_control_level_id - dvs.append(dv) + timeseries = TimeSeriesResultValues() + timeseries.DataValue = i + timeseries.TimeAggregationInterval = datetime.datetime.now() - datetime.timedelta(days=i) + timeseries.ValueDateTimeUTCOffset = 0 + timeseries.ValueDateTime = timeseries.TimeAggregationInterval + timeseries.CensorCodeCV = "NC" + timeseries.QualityCodeCV = series.quality_control_level + dvs.append(timeseries) series.data_values = dvs session.add_all(dvs) From 304d63584b8dc71099d74fc87ac0304652c45704 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Mon, 24 Oct 2016 10:50:27 -0600 Subject: [PATCH 040/158] API updates part 1 --- odmtools/odmservices/series_service.py | 61 ++++++++++++++------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 83f40a7..d8fba79 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -124,31 +124,35 @@ def get_variables_by_site_code(self, site_code): # Series Catalog methods -# -# def get_series_by_site(self , site_id): -# """ -# -# :param site_id: int -# :return: List[Series] -# """ -# try: -# selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() -# return selectedSeries -# except: -# return None + def get_series_by_site(self , site_id): + """ + + :param site_id: int + :return: List[Series] + """ + # try: + # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() + # return selectedSeries + # except: + # return None + + return self.read.getResult(type="site", ids = [site_id])[0] + ##TODO : check is this the right way to get the series?? + + + + + # Site methods + def get_all_sites(self): + """ + + :return: List[Sites] + """ + #return self._edit_session.query(Site).order_by(Site.code).all() + return self.read.getResults(type="site") -# -# # Site methods -# def get_all_sites(self): -# """ -# -# :return: List[Sites] -# """ -# return self._edit_session.query(Site).order_by(Site.code).all() -# -# # def get_site_by_id(self, site_id): """ @@ -166,12 +170,13 @@ def get_site_by_id(self, site_id): # # -# def get_all_variables(self): -# """ -# -# :return: List[Variables] -# """ -# return self._edit_session.query(Variable).all() + def get_all_variables(self): + """ + + :return: List[Variables] + """ + #return self._edit_session.query(Variable).all() + return self.read.getVariables() # def get_variable_by_id(self, variable_id): """ From ea0921f367ff6d2a070e4510c80660cd81537dbd Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 24 Oct 2016 14:27:54 -0600 Subject: [PATCH 041/158] merge conflicts --- odmtools/common/taskServer.py | 9 +- odmtools/controller/WizardMethodController.py | 2 +- .../WizardProcessLevelController.py | 3 +- .../controller/WizardVariableController.py | 4 +- odmtools/gui/frmODMTools.py | 3 - odmtools/gui/pageMethod.py | 123 ----- odmtools/gui/plotProbability.py | 4 +- odmtools/odmdata/__init__.py | 33 -- odmtools/odmdata/memory_database.py | 6 +- odmtools/odmservices/__init__.py | 10 - odmtools/odmservices/series_service.py | 516 +----------------- odmtools/odmservices/service_manager.py | 52 +- tests/test_odmservices/test_export_service.py | 7 +- tests/test_odmservices/test_series_service.py | 10 +- 14 files changed, 28 insertions(+), 754 deletions(-) diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index 3b03850..ffaf6db 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -131,13 +131,12 @@ def worker(cls, dispatcher): if task_type == "InitEditValues": connection = SeriesService("sqlite:///:memory:") df = task[1] -<<<<<<< HEAD logger.debug("Load series from db") df.to_sql(name="DataValues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) -======= - logger.debug("Load series_service from db") - df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) ->>>>>>> origin/update_cvs +# ======= +# logger.debug("Load series_service from db") +# df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) +# >>>>>>> origin/update_cvs logger.debug("done loading database") result = connection if task_type == "UpdateEditDF": diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 9272987..5a31d95 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -1,7 +1,7 @@ import wx from wx.wizard import WizardPageSimple from odmtools.view.WizardMethodView import WizardMethodView -from odmtools.odmdata import Method +from odm2api.ODM2.models import Methods as Method class WizardMethodController(WizardPageSimple): diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index f172453..f36d1b7 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -1,7 +1,8 @@ import wx from odmtools.view.WizardProcessLevelView import WizardProcessLevelView from wx.wizard import WizardPageSimple -from odmtools.odmdata import QualityControlLevel +# from odmtools.odmdata import QualityControlLevel +from odm2api.ODM2.models import ProcessingLevels as QualityControlLevel class WizardProcessLevelController(WizardPageSimple): diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index 2038b5f..c6417e8 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -1,8 +1,8 @@ import wx from odmtools.view.WizardVariableView import WizardVariableView from wx.wizard import WizardPageSimple -from odmtools.odmdata import Variable - +# from odmtools.odmdata import Variable +from odm2api.ODM2.models import Variables as Variable class WizardVariableController(WizardPageSimple): def __init__(self, parent, service_manager, current_variable): diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index 354a422..0d936b2 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -123,10 +123,7 @@ def _init_s_Items(self, parent): def _init_database(self, quit_if_cancel=True): logger.info("Loading Database...") -<<<<<<< HEAD -======= ->>>>>>> origin/update_cvs while True: ## Database connection is valid, therefore proceed through the rest of the program if self.service_manager.is_valid_connection(): diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py index 25336db..e057d18 100644 --- a/odmtools/gui/pageMethod.py +++ b/odmtools/gui/pageMethod.py @@ -18,129 +18,7 @@ # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) logger =logging.getLogger('main') -<<<<<<< HEAD -class pnlMethod(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLMETHOD, name=u'pnlMethod', - parent=prnt, pos=wx.Point(135, 307), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbGenerate = wx.RadioButton(id=wxID_PNLMETHODSRBGENERATE, - label=u'Automatically generate a Method ', name=u'rbGenerate', - parent=self, pos=wx.Point(16, 8), size=wx.Size(392, 16), style=0) - self.rbGenerate.SetValue(True) - self.rbGenerate.Bind(wx.EVT_RADIOBUTTON, self.OnRbGenerateRadiobutton, - id=wxID_PNLMETHODSRBGENERATE) - - self.rbSelect = wx.RadioButton(id=wxID_PNLMETHODSRBSELECT, - label=u'Select an existing Method', name=u'rbSelect', parent=self, - pos=wx.Point(16, 32), size=wx.Size(392, 13), style=0) - self.rbSelect.SetValue(False) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLMETHODSRBSELECT) - - self.rbCreateNew = wx.RadioButton(id=wxID_PNLMETHODSRBCREATENEW, - label=u'Create a new Method ', name=u'rbCreateNew', parent=self, - pos=wx.Point(16, 208), size=wx.Size(392, 13), style=0) - self.rbCreateNew.SetValue(False) - self.rbCreateNew.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateNewRadiobutton, - id=wxID_PNLMETHODSRBCREATENEW) - - self.txtMethodDescrip = wx.richtext.RichTextCtrl(id=wxID_PNLMETHODSRICHTEXTCTRL1, - parent=self, pos=wx.Point(16, 224), size=wx.Size(392, 84), - style=wx.richtext.RE_MULTILINE, value=u'Method Description') - self.txtMethodDescrip.Enable(False) - self.txtMethodDescrip.Bind(wx.EVT_SET_FOCUS, self.OnTxtMethodDescripSetFocus) - self.txtMethodDescrip.Bind(wx.EVT_KILL_FOCUS, self.OnTxtMethodDescripKillFocus) - - self.lstMethods = wx.ListCtrl(id=wxID_PNLMETHODSLISTCTRL1, - name='lstMethods', parent=self, pos=wx.Point(16, 48), - size=wx.Size(392, 152), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstMethods.Bind(wx.EVT_SET_FOCUS, self.OnLstMethodSetFocus) - - - self.lstMethods.InsertColumn(0, 'Description') - self.lstMethods.InsertColumn(1, 'Link') - self.lstMethods.InsertColumn(2, 'id') - self.lstMethods.SetColumnWidth(0, 200) - self.lstMethods.SetColumnWidth(1, 153) - self.lstMethods.SetColumnWidth(2,0) - # self.lstMethods.Enable(False) - - - - - def __init__(self, parent, id, pos, size, style, name, ss, method): - self.series_service = ss - self.prev_val = method - self._init_ctrls(parent) - - def OnLstMethodSetFocus(self, event): - self.rbSelect.SetValue(True) - - def OnRbGenerateRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbSelectRadiobutton(self, event): - self.lstMethods.Enable(True) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbCreateNewRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(True) - - event.Skip() - - def OnTxtMethodDescripSetFocus(self, event): - if self.txtMethodDescrip.GetValue() =="Method Description": - self.txtMethodDescrip.SetValue("") - - event.Skip() - - def OnTxtMethodDescripKillFocus(self, event): - if self.txtMethodDescrip.GetValue() =="": - self.txtMethodDescrip.SetValue("Method Description") - - event.Skip() - - - def getMethod(self): - - m = None - if self.rbGenerate.Value: - genmethod = "Values derived from ODM Tools Python" - - m= self.series_service.get_method_by_description(genmethod) - if m is None: - logger.debug("assigning new method description") - m = Methods() - m.MethodDescription = genmethod - - - elif self.rbSelect.Value: - index = self.lstMethods.GetFirstSelected() - desc= self.lstMethods.GetItem(index, 0).GetText() - - logger.debug(desc) - m= self.series_service.get_method_by_description(desc) - - - - elif self.rbCreateNew.Value: - - logger.debug("assigning new method description") - m.description = self.txtMethodDescrip.GetValue() - - return m -======= class pnlMethod(wx.Panel): # Rename this to page method view def __init__(self, parent): wx.Panel.__init__(self, parent) @@ -324,4 +202,3 @@ def __init__(self, parent): # logger.debug("assigning new method description") # m.description = self.txtMethodDescrip.GetValue() # return m ->>>>>>> origin/update_cvs diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index ac13da1..874649e 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -133,12 +133,10 @@ def updatePlot(self): self.plots.set_title("\n".join(textwrap.wrap(oneSeries.siteName, 55))) if len(oneSeries.dataTable) > 0: -<<<<<<< HEAD + #self.prob.append( #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) #todo FutureWarning: order is deprecated, use sort_values(...) -======= ->>>>>>> origin/update_cvs xValues = oneSeries.Probability.xAxis.sort_values().values yValues = oneSeries.Probability.yAxis.sort_values().values diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 7c8ddd9..743963e 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,36 +1,3 @@ -<<<<<<< HEAD -======= -from base import Base -from censor_code_cv import CensorCodeCV -from data_type_cv import DataTypeCV -from data_value import DataValue -from general_category_cv import GeneralCategoryCV -from iso_metadata import ISOMetadata -from lab_method import LabMethod -from method import Method -from odm_version import ODMVersion -from offset_type import OffsetType -from qualifier import Qualifier -from quality_control_level import QualityControlLevel -from sample import Sample -from sample_medium_cv import SampleMediumCV -from sample_type_cv import SampleTypeCV -from series import Series -from odm2api.ODMconnection import SessionFactory -from site import Site -from site_type_cv import SiteTypeCV -from source import Source -from spatial_reference import SpatialReference -from speciation_cv import SpeciationCV -from topic_category_cv import TopicCategoryCV -from unit import Unit -from value_type_cv import ValueTypeCV -from variable import Variable -from variable_name_cv import VariableNameCV -from vertical_datum_cv import VerticalDatumCV -from memory_database import MemoryDatabase ->>>>>>> origin/update_cvs - #from odm2api.ODM1_1_1.services import SeriesService#, refreshDB diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 2461089..885e373 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -230,11 +230,7 @@ def initEditValues(self, seriesID): self.df.to_sql(name="DataValues", if_exists='replace', con=self.mem_service._session_factory.engine, index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") -<<<<<<< HEAD -======= - else: - logger.debug("no data in series_service") ->>>>>>> origin/update_cvs + #TODO: update to work with ODM2 diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 1f3cc5d..22e89c4 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -11,22 +11,12 @@ from odm2api.ODM1_1_1.services import EditService, ExportService#, , CVService, SeriesService, from series_service import SeriesService -<<<<<<< HEAD from service_manager import ServiceManager -======= -from ReadService import ReadService -from edit_service import EditService -from export_service import ExportService ->>>>>>> origin/update_cvs __all__ = [ 'EditService', -<<<<<<< HEAD #'CVService', -======= - 'ReadService', ->>>>>>> origin/update_cvs 'SeriesService', 'ExportService', 'ServiceManager', diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index c25aecb..e8f8cc4 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,34 +1,18 @@ + import logging + + from sqlalchemy import distinct, func -<<<<<<< HEAD from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -======= -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import Site -# from odmtools.odmdata import Variable -from odm2api.ODM2.models import * -from odmtools.odmdata import Series -from odmtools.odmdata import DataValue -from odmtools.odmdata import Qualifier -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -# from odmtools.odmdata import Method -from odmtools.odmdata import QualityControlLevel -from odmtools.odmdata import ODMVersion ->>>>>>> origin/update_cvs from odmtools.common.logger import LoggerTool import pandas as pd -from odm2api.ODM2.services.createService import CreateODM2 -from odm2api.ODM2.services.readService import * - logger =logging.getLogger('main') -<<<<<<< HEAD class SeriesService(serviceBase): # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg @@ -49,19 +33,6 @@ def reset_session(self): -======= -class SeriesService(): # Rename to CreateService - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.getSession() - self._debug = debug - self.create_service = CreateODM2(session_factory=self._session_factory, debug=self._debug) - self.read_service = ReadODM2(self._session_factory, debug=self._debug) - - def reset_session(self): - self._edit_session = self._session_factory.getSession() # Reset the session in order to prevent memory leaks ->>>>>>> origin/update_cvs ##################### @@ -70,17 +41,6 @@ def reset_session(self): # ##################### -<<<<<<< HEAD -======= - # Site methods - def get_all_sites(self): # Site -> Sampling Feature in ODM2 - """ - - :return: List[Sites] - """ - # return self._edit_session.query(Site).order_by(Site.code).all() - return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None,type=None, wkt=None) ->>>>>>> origin/update_cvs def get_used_sites(self): @@ -89,18 +49,8 @@ def get_used_sites(self): :return: List[Sites] """ try: -<<<<<<< HEAD fas=[x[0] for x in self._session.query(distinct(Results.FeatureActionID)).all()] except: -======= - site_ids = [site_ids[0] for site_ids in self._edit_session.query(distinct(Results.FeatureActionID)).all()] - except: - site_ids = None - - site_ids = self._edit_session.query(FeatureActions.SamplingFeatureID).filter(FeatureActions.FeatureActionID.in_(site_ids)).all() - - if not site_ids: ->>>>>>> origin/update_cvs return None sf=[x[0] for x in self._session.query(distinct(FeatureActions.SamplingFeatureID)).filter(FeatureActions.FeatureActionID.in_(fas)).all()] @@ -108,29 +58,12 @@ def get_used_sites(self): sites = self.read.getSamplingFeatures(type = "site", ids = sf) return sites -<<<<<<< HEAD -======= - def get_site_by_id(self, site_id): - """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites - """ - # try: - # return self._edit_session.query(Site).filter_by(id=site_id).first() - # except: - # return None - return self.read_service.getSamplingFeatures(ids=site_id) - - # Variables methods ->>>>>>> origin/update_cvs def get_used_variables(self): """ #get list of used variable ids :return: List[Variables] """ try: -<<<<<<< HEAD ids= [x[0] for x in self._session.query(distinct(Results.VariableID)).all()] except: return None @@ -139,63 +72,18 @@ def get_used_variables(self): return vars -======= - # var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] - var_ids = [x[0] for x in self._edit_session.query(distinct(Results.VariableID)).all()] - except: - var_ids = None - - var_ids = self._edit_session.query(Results.VariableID).filter(Results.VariableID.in_(var_ids)).all() - if not var_ids: - return None - - Variables = [] - - #create list of variables from the list of ids - for var_id in var_ids: - Variables.append(self._edit_session.query(Variables).filter_by(id=var_id).first()) - - return Variables - - def get_all_variables(self): - """ - :return: List[Variables] - """ - return self.read_service.getVariables(ids=None, codes=None) ->>>>>>> origin/update_cvs # Query DetailedResultInfo/series object is for Display purposes def get_all_series(self): """ -<<<<<<< HEAD Returns all series as a modelObject :return: List[Series] -======= - return self.read_service.getVariables(ids=variable_id) - - def get_variable_by_code(self, variable_code): ->>>>>>> origin/update_cvs """ return self.read.getDetailedResultInfo('Time Series Coverage') -<<<<<<< HEAD def get_series_by_id(self, series_id): -======= - :param variable_code: str - :return: Variables """ - return self.read_service.getVariables(codes=variable_code) - - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] ->>>>>>> origin/update_cvs - """ - -<<<<<<< HEAD :param series_id: int :return: Series """ @@ -212,24 +100,9 @@ def get_result_dates(self, result_id): func.max(TimeSeriesResultValues.ValueDateTime), func.min(TimeSeriesResultValues.ValueDateTime) ).filter(TimeSeriesResultValues.ResultID == result_id) return q.all()[0] -======= - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variables).filter_by(id=var_id).first()) - - return variables - - # Unit methods - def get_all_units(self): - """ - :return: List[Units] - """ - return self.read_service.getUnits(ids=None, name=None, type=None) ->>>>>>> origin/update_cvs def get_variables_by_site_code(self, site_code): """ -<<<<<<< HEAD Finds all of variables at a site :param site_code: str :return: List[Variables] @@ -251,43 +124,12 @@ def get_variables_by_site_code(self, site_code): # Series Catalog methods -======= - :param unit_name: str - :return: Units - """ - return self.read_service.getUnits(name=unit_name) - - def get_unit_by_id(self, unit_id): - """ - :param unit_id: int - :return: Units - """ - self.read_service.getUnits(ids=unit_id) - - def get_all_qualifiers(self): # Rename to annotations - """ - :return: List[Qualifiers] - """ - # result = self._edit_session.query(Annotations).order_by(Annotations.AnnotationCode).all() - # return result - return self.read_service.getAnnotations(None) - - def get_qualifier_by_code(self, code): # Rename to get_annotations_by_type - """ - :return: Qualifiers - """ - # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - # return result - return self.read_service.getAnnotations(type=code) ->>>>>>> origin/update_cvs def get_series_by_site(self , site_id): """ - :param site_id: int :return: List[Series] """ -<<<<<<< HEAD # try: # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() # return selectedSeries @@ -298,35 +140,11 @@ def get_series_by_site(self , site_id): ##TODO : check is this the right way to get the series?? -======= - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() - - def get_all_processing_levels(self): - return self.read_service.getProcessingLevels(ids=None, codes=None) - - def get_processing_level_by_id(self, proc_level_id): - return self.read_service.getProcessingLevels(ids=proc_level_id) - - def get_processing_level_by_code(self, proc_level_code): - return self.read_service.getProcessingLevels(codes=proc_level_code) - - def get_all_methods(self): - return self.read_service.getMethods(ids=None, codes=None, type=None) - - def get_method_by_id(self, method_id): - return self.read_service.getMethods(ids=method_id) - - def get_method_by_code(self, method_code): - return self.read_service.getMethods(codes=method_code) ->>>>>>> origin/update_cvs # Site methods def get_all_sites(self): """ - :return: List[Sites] """ #return self._edit_session.query(Site).order_by(Site.code).all() @@ -336,14 +154,9 @@ def get_all_sites(self): # def get_site_by_id(self, site_id): """ -<<<<<<< HEAD return a Site object that has an id=site_id :param site_id: integer- the identification number of the site :return: Sites -======= - Returns all series_service as a modelObject - :return: List[Series] ->>>>>>> origin/update_cvs """ # try: # return self._edit_session.query(Site).filter_by(id=site_id).first() @@ -357,7 +170,6 @@ def get_site_by_id(self, site_id): # def get_all_variables(self): """ - :return: List[Variables] """ #return self._edit_session.query(Variable).all() @@ -365,7 +177,6 @@ def get_all_variables(self): # def get_variable_by_id(self, variable_id): """ - :param variable_id: int :return: Variables """ @@ -559,7 +370,6 @@ def get_variable_by_id(self, variable_id): #Data Value Methods def get_values(self, series_id=None): ''' - :param series_id: Series id :return: pandas dataframe ''' @@ -666,7 +476,6 @@ def calcSeason(row): # def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): """ - :param seriesID: :param noDataValue: :param startDate: @@ -717,240 +526,8 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non # except: # return None # -<<<<<<< HEAD -======= -##################### - def save_series(self, series, dvs): - """ Save to an Existing Series - :param series: - :param data_values: - :return: - """ - - if self.series_exists(series): - - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - logger.info("Existing File was overwritten with new information") - return True - else: - logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") - # there wasn't an existing file to overwrite - raise Exception("Series does not exist, unable to save. Please select 'Save As'") - - - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.info(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - logger.info("A new series_service was added to the database, series_service id: "+str(series.id)) - return True - - def save_values(self, values): - """ - - :param values: pandas dataframe - :return: - """ - values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) - - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - """ - series_service -> Result in ODM2 - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Series() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - - return self.create_service.createResult(series) - - def create_method(self, description, link): - """ - :param description: - :param link: - :return: - """ - method = Methods() - method.MethodDescription = description - if link is not None: - method.MethodLink = link - - return self.create_service.createMethod(method=method) - - def create_variable_by_var(self, var): - """ - :param var: Variable Object - :return: - """ - return self.create_service.createVariable(var=var) - - def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): - """ - :param code: - :param name: - :param speciation: - :param variable_unit_id: - :param sample_medium: - :param value_type: - :param is_regular: - :param time_support: - :param time_unit_id: - :param data_type: - :param general_category: - :param no_data_value: - :return: - """ - # var = Variable() - variable = Variables() - variable.VariableCode = code - variable.VariableNameCV = name - variable.SpeciationCV = speciation - # Commented lines indicate that Variables does not have such attributes - # var.variable_unit_id = variable_unit_id - # var.sample_medium = sample_medium - # var.value_type = value_type - # var.is_regular = is_regular - # var.time_support = time_support - # var.time_unit_id = time_unit_id - # var.data_type = data_type - # var.general_category = general_category - variable.NoDataValue = no_data_value - - return self.create_service.createVariable(var=variable) - - def create_processing_level(self, code, definition, explanation): - """ - qcl -> Processing Level in ODM2 - :param code: - :param definition: - :param explanation: - :return: - """ - procLevel = ProcessingLevels() - procLevel.ProcessingLevelCode = code - procLevel.Definition = definition - procLevel.Explanation = explanation - return self.create_service.createProcessingLevel(proclevel=procLevel) - - def create_annotation_by_anno(self, annotation): - return self.create_service.create(annotation) - - def create_annotation(self, code, description): - """ - :param code: - :param description: - :return: - """ - annotation = Annotations() - annotation.AnnotationCode = code - annotation.AnnotationText = description - annotation.AnnotationTypeCV = "timeSeriesResultValueAnnotation" - - return self.create_annotation_by_anno(annotation) - -##################### ->>>>>>> origin/update_cvs # # -<<<<<<< HEAD -======= -##################### - - def delete_series(self, series): - """ - - :param series: - :return: - """ - try: - self.delete_values_by_series(series) - - delete_series = self._edit_session.merge(series) - self._edit_session.delete(delete_series) - self._edit_session.commit() - except Exception as e: - message = "series_service was not successfully deleted: %s" % e - print message - logger.error(message) - raise e - - - def delete_values_by_series(self, series, startdate = None): - """ - - :param series: - :return: - """ - try: - q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - variable_id = series.variable_id, - method_id = series.method_id, - source_id = series.source_id, - quality_control_level_id = series.quality_control_level_id) - if startdate is not None: - #start date indicates what day you should start deleting values. the values will delete to the end of the series_service - return q.filter(DataValue.local_date_time >= startdate).delete() - else: - return q.delete() - - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex - - def delete_dvs(self, id_list): - """ - - :param id_list: list of datetimes - :return: - """ - try: - self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex - -##################### ->>>>>>> origin/update_cvs # # ##################### # # @@ -960,7 +537,6 @@ def delete_dvs(self, id_list): # def update_series(self, series): # """ # -<<<<<<< HEAD # :param series: # :return: # """ @@ -1316,89 +892,3 @@ def delete_dvs(self, id_list): # return result # except: # return None -======= -##################### - - - def series_exists(self, series): - """ - - :param series: - :return: - """ - return self.series_exists_quint( - series.site_id, - series.variable_id, - series.method_id, - series.source_id, - series.quality_control_level_id - ) - - def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ - - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - try: - result = self._edit_session.query(Series).filter_by( - site_id=site_id, - variable_id=var_id, - method_id=method_id, - source_id=source_id, - quality_control_level_id=qcl_id - ).one() - - return True - except: - return False - - def qcl_exists(self, q): - """ - - :param q: - :return: - """ - try: - result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() - return True - except: - - return False - - def method_exists(self, m): - """ - - :param m: - :return: - """ - try: - result = self._edit_session.query(Methods).filter_by(description=m.description).one() - return True - except: - return False - - def variable_exists(self, v): - """ - - :param v: - :return: - """ - try: - result = self._edit_session.query(Variables).filter_by(code=v.code, - name=v.name, speciation=v.speciation, - variable_unit_id=v.variable_unit_id, - sample_medium=v.sample_medium, - value_type=v.value_type, is_regular=v.is_regular, - time_support=v.time_support, - time_unit_id=v.time_unit_id, data_type=v.data_type, - general_category=v.general_category, - no_data_value=v.no_data_value).one() - return result - except: - return None ->>>>>>> origin/update_cvs diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index c8b7eb5..72462ef 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -4,27 +4,16 @@ import urllib -<<<<<<< HEAD + from sqlalchemy.exc import SQLAlchemyError from odmtools.odmservices import SeriesService, EditService, ExportService -======= -from sqlalchemy.exc import SQLAlchemyError#OperationalError, DBAPIError - -from odmtools.common.logger import LoggerTool -from series_service import SeriesService -from ReadService import ReadService -from edit_service import EditService ->>>>>>> origin/update_cvs + + from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -<<<<<<< HEAD -from odmtools.odmdata import dbconnection #ODM +from odmtools.odmdata import dbconnection #ODM -======= -# from odmtools.odmdata.session_factory import SessionFactory -from odm2api.ODMconnection import SessionFactory ->>>>>>> origin/update_cvs # tool = LoggerTool() @@ -89,36 +78,14 @@ def add_connection(self, conn_dict): # remove earlier connections that are identical to this one self.delete_connection(conn_dict) -<<<<<<< HEAD + #assume connection has already been tested # if self.test_connection(conn_dict): # write changes to connection file self._conn_dicts.append(conn_dict) self._current_conn_dict = self._conn_dicts[-1] self._save_connections() -======= - if self.test_connection(conn_dict): - # write changes to connection file - self._conn_dicts.append(conn_dict) - self._current_conn_dict = self._conn_dicts[-1] - self._save_connections() - return True - else: - logger.error("Unable to save connection due to invalid connection to database") - return False - - - @classmethod - def testEngine(self, connection_string): - s = SessionFactory(connection_string, echo=False) - if 'mssql' in connection_string: - s.test_Session().execute("Select top 1 VariableCode From Variables") - elif 'mysql' in connection_string: - s.test_Session().execute('Select "VariableCode" From Variables Limit 1') - elif 'postgresql' in connection_string: - #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.test_Session().execute('Select "VariableCode" From "Variables" Limit 1') ->>>>>>> origin/update_cvs + return True # else: # logger.error("Unable to save connection due to invalid connection to database") @@ -163,7 +130,6 @@ def get_series_service(self, conn_dict=None, conn_string=""): if not conn_dict: conn_dict = self.get_current_conn_dict() -<<<<<<< HEAD if conn_string: #todo how to get version from a connection string conn = dbconnection.createConnectionFromString(conn_string, float(self.get_current_conn_dict()["version"])) @@ -186,14 +152,10 @@ def get_series_service(self, conn_dict=None, conn_string=""): ss= SeriesService(conn) return ss - # def get_cv_service(self): - # conn_string = self._build_connection_string(self._current_conn_dict) - # return CVService(SessionFactory(conn_string, self.debug)) -======= def get_cv_service(self): conn_string = self._build_connection_string(self._current_conn_dict) return ReadService(conn_string, self.debug) ->>>>>>> origin/update_cvs + def get_edit_service(self, series_id, connection): return EditService(series_id, connection=connection, debug=self.debug) diff --git a/tests/test_odmservices/test_export_service.py b/tests/test_odmservices/test_export_service.py index 168cf92..2cc8539 100644 --- a/tests/test_odmservices/test_export_service.py +++ b/tests/test_odmservices/test_export_service.py @@ -8,13 +8,10 @@ class TestExportService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) -<<<<<<< HEAD + self.session = self.series_service._connection.get_session() engine = self.series_service._connection.engine -======= - self.session = self.series_service._session_factory.getSession() - engine = self.series_service._session_factory.engine ->>>>>>> origin/update_cvs + test_util.build_db(engine) self.series = test_util.add_series(self.session) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 4b5b285..3cefbe1 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -9,13 +9,13 @@ class TestSeriesService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) -<<<<<<< HEAD - self.session = self.series_service._connection.get_session() - engine = self.series_service._connection.engine -======= +# +# self.session = self.series_service._connection.get_session() +# engine = self.series_service._connection.engine +# ======= self.session = self.series_service._session_factory.getSession() engine = self.series_service._session_factory.engine ->>>>>>> origin/update_cvs + test_util.build_db(engine) """ @pytest.fixture(scope="class", autouse=True) From be1862aac90e4a97105c794f7564297be15296fb Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 25 Oct 2016 11:03:03 -0600 Subject: [PATCH 042/158] a lot of "get" api updates --- odmtools/odmservices/series_service.py | 57 ++++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index e8f8cc4..2c739a0 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -186,37 +186,40 @@ def get_variable_by_id(self, variable_id): # return None return self.read.getVariables(ids = [variable_id])[0] # -# def get_variable_by_code(self, variable_code): -# """ -# -# :param variable_code: str -# :return: Variables -# """ -# try: -# return self._edit_session.query(Variable).filter_by(code=variable_code).first() -# except: -# return None -# + def get_variable_by_code(self, variable_code): + """ + :param variable_code: str + :return: Variables + """ + # try: + # return self._edit_session.query(Variable).filter_by(code=variable_code).first() + # except: + # return None + return self.read.getVariables(codes = [variable_code])[0] # -# # Unit methods -# def get_all_units(self): -# """ -# -# :return: List[Units] -# """ -# return self._edit_session.query(Unit).all() + # -# def get_unit_by_name(self, unit_name): -# """ + # Unit methods + def get_all_units(self): + """ + + :return: List[Units] + """ + # return self._edit_session.query(Unit).all() + return self.read.getUnits() # -# :param unit_name: str -# :return: Units -# """ -# try: -# return self._edit_session.query(Unit).filter_by(name=unit_name).first() -# except: -# return None + def get_unit_by_name(self, unit_name): + """ + + :param unit_name: str + :return: Units + """ + # try: + # return self._edit_session.query(Unit).filter_by(name=unit_name).first() + # except: + # return None + return self.read.getUnits(name = [unit_name])[0] # # def get_unit_by_id(self, unit_id): # """ From cdc05ae8fb257b2252eda65c8ace941f623e183f Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 25 Oct 2016 12:26:56 -0600 Subject: [PATCH 043/158] more get api updates --- odmtools/odmservices/series_service.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 2c739a0..4890965 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -211,7 +211,6 @@ def get_all_units(self): # def get_unit_by_name(self, unit_name): """ - :param unit_name: str :return: Units """ @@ -221,17 +220,18 @@ def get_unit_by_name(self, unit_name): # return None return self.read.getUnits(name = [unit_name])[0] # -# def get_unit_by_id(self, unit_id): -# """ -# -# :param unit_id: int -# :return: Units -# """ -# try: -# return self._edit_session.query(Unit).filter_by(id=unit_id).first() -# except: -# return None -# + def get_unit_by_id(self, unit_id): + """ + + :param unit_id: int + :return: Units + """ + # try: + # return self._edit_session.query(Unit).filter_by(id=unit_id).first() + # except: + # return None + return self.read.getUnits(ids = [unit_id])[0] + # # def get_all_qualifiers(self): # """ From c9d4ae99dab0b9e91457531155a1328e93588d67 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Wed, 26 Oct 2016 10:22:58 -0600 Subject: [PATCH 044/158] api updates --- odmtools/odmservices/series_service.py | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4890965..b9f3a52 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -233,21 +233,23 @@ def get_unit_by_id(self, unit_id): return self.read.getUnits(ids = [unit_id])[0] # -# def get_all_qualifiers(self): -# """ -# -# :return: List[Qualifiers] -# """ -# result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() -# return result -# -# def get_qualifier_by_code(self, code): -# """ + def get_all_qualifiers(self): + """ + + :return: List[Qualifiers] + """ + # result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() + # return result + return self.read.getAnnotations() # -# :return: Qualifiers -# """ -# result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() -# return result + def get_qualifier_by_code(self, code): + """ + + :return: Qualifiers + # """ + # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + # return result + return self.read.getAnnotations() ##todo: CHECK ON THIS # # def get_qualifiers_by_series_id(self, series_id): # """ From 04671ad8edcfe330e19392d3dcca97aea03b9ea4 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Fri, 28 Oct 2016 11:24:01 -0600 Subject: [PATCH 045/158] model gets --- odmtools/odmservices/series_service.py | 33 ++++++++++++++------------ 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index b9f3a52..8e3fc5d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -278,23 +278,26 @@ def get_qualifier_by_code(self, code): # return None # # # Method methods -# def get_all_methods(self): -# return self._edit_session.query(Method).all() + def get_all_methods(self): + #return self._edit_session.query(Method).all() + return self.read.getMethods() # -# def get_method_by_id(self, method_id): -# try: -# result = self._edit_session.query(Method).filter_by(id=method_id).first() -# except: -# result = None -# return result + def get_method_by_id(self, method_id): + return self.read.getMethods(ids=[method_id])[0] + # try: + # result = self._edit_session.query(Method).filter_by(id=method_id).first() + # except: + # result = None + # return result # -# def get_method_by_description(self, method_code): -# try: -# result = self._edit_session.query(Method).filter_by(description=method_code).first() -# except: -# result = None -# logger.error("method not found") -# return result + def get_method_by_description(self, method_code): + return self.read.getMethods(codes=[method_code])[0] + # try: + # result = self._edit_session.query(Method).filter_by(description=method_code).first() + # except: + # result = None + # logger.error("method not found") + # return result # # def get_offset_types_by_series_id(self, series_id): # """ From 04ff4e440dd78541288499a87c4e379db3f94d89 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Fri, 28 Oct 2016 12:37:34 -0600 Subject: [PATCH 046/158] Updating the AddPointForm started changing the column headers and populating it with data --- odmtools/controller/logicCellEdit.py | 8 ++++ odmtools/controller/olvAddPoint.py | 65 ++++++++-------------------- 2 files changed, 26 insertions(+), 47 deletions(-) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index f2b11ed..fc063c1 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -412,6 +412,14 @@ def labSampleCodeEditor(self, olv, rowIndex, subItemIndex): odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb + def valueDateTimeEditor(self, olv, rowIndex, subItemIndex): + odcb = DatePicker(olv) + odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return odcb + + + + class DatePicker(wx.DatePickerCtrl): """ This control uses standard datetime. diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index bc9c7a2..f942377 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -1,26 +1,13 @@ -""" - Object List View Control used in Add Point Form -""" import wx import wx.lib.newevent from datetime import datetime from odmtools.common.icons import x_mark_16, star_16, star_32, x_mark_32, check_mark_3_16, check_mark_3_32 from odmtools.controller.logicCellEdit import CellEdit, NULL - - -OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() - - -__author__ = 'Jacob' - from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn +OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() class Points(object): - """ - - """ - def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, censorCode="NULL", valueAccuracy="NULL", offSetValue="NULL", offSetType="NULL", qualifierCode="NULL", labSampleCode="NULL"): @@ -37,9 +24,10 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.time = time self.date = str(date) + self.valueDateTime = self.date self.utcOffSet = str(utcOffSet) - #self.dateTimeUTC = dateTimeUTC + self.valueDateTimeUTFOffset = -1 self.offSetValue = offSetValue self.offSetType = offSetType self.censorCode = censorCode @@ -57,28 +45,10 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.validOffSetType = False self.validQualifierCode = False self.validLabSampleCode = False - def __repr__(self): - """ - - :return: - """ - - return "" \ - % (self.dataValue, self.date, self.time, self.utcOffSet, self.censorCode) class OLVAddPoint(FastObjectListView): - """ - - """ def __init__(self, *args, **kwargs): - """ - - :param args: - :param kwargs: - :return: - """ - try: self.serviceManager = kwargs.pop("serviceManager") except: @@ -88,7 +58,6 @@ def __init__(self, *args, **kwargs): except: self.recordService = None - FastObjectListView.__init__(self, *args, **kwargs) cellEdit = CellEdit(self, self.serviceManager, self.recordService) @@ -127,6 +96,8 @@ def __init__(self, *args, **kwargs): self.offSetTypeEditor = cellEdit.offSetTypeEditor self.qualifierCodeEditor = cellEdit.qualifierCodeEditor self.labSampleEditor = cellEdit.labSampleCodeEditor + self.valueDateTimeEditorCreator = cellEdit.valueDateTimeEditor + # self.valueDateTimeUTFOffset = cellEdit.valueDateTimeUTFOffsetCreator self.SetEmptyListMsg("Add points either by csv or by adding a new row") self.AddNamedImages("error", x_mark_16.GetBitmap(), x_mark_32.GetBitmap()) @@ -141,8 +112,6 @@ def __init__(self, *args, **kwargs): def buildOlv(self): columns = [ - ## TODO This is needed for the windows version - #ColumnDefn("", "left", -1, valueSetter=self.emptyCol), ColumnDefn("DataValue", "left", -1, minimumWidth=100, valueGetter='dataValue', valueSetter=self.valueSetterDataValue, @@ -160,18 +129,19 @@ def buildOlv(self): cellEditorCreator=self.timeEditor, stringConverter=self.localtime2Str, headerImage="star"), - ColumnDefn("UTCOffset", "left", -1, minimumWidth=100, - valueGetter="utcOffSet", - #valueSetter=self.valueSetterUTCOffset, - #stringConverter=self.utcOffSet2Str, - imageGetter=self.imgGetterUTCOffset, - headerImage="star"), + ColumnDefn("UTCOffset", "left", -1, minimumWidth=100, valueGetter="utcOffSet", + imageGetter=self.imgGetterUTCOffset, headerImage="star"), + ColumnDefn("CensorCode", "left", -1, valueGetter="censorCode", minimumWidth=110, - cellEditorCreator=self.censorEditor, - imageGetter=self.imgGetterCensorCode, - headerImage="star"), - ColumnDefn("ValueAccuracy", "left", -1, valueGetter="valueAccuracy", minimumWidth=100, - imageGetter=self.imgGetterValueAcc), + cellEditorCreator=self.censorEditor, imageGetter=self.imgGetterCensorCode, headerImage="star"), + + # valueGetter needs to be created in the Points class + ColumnDefn(title="ValueDateTime", align="left", valueGetter="valueDateTime", + minimumWidth=123, cellEditorCreator=self.valueDateTimeEditorCreator, headerImage="star"), + + ColumnDefn(title="ValueDateTimeUTFOffset", align="left", valueGetter="valueDateTimeUTFOffset", + minimumWidth=130, headerImage="star"), + ColumnDefn("OffsetValue", "left", -1, valueGetter="offSetValue", minimumWidth=100, stringConverter=self.offSetValue2Str, imageGetter=self.imgGetterOffSetValue), @@ -202,6 +172,7 @@ def rowFormatter(listItem, point): listItem.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)) self.rowFormatter = rowFormatter + self.AutoSizeColumns() def isCorrect(self, point): validators = [ From 055bb459f3be87f04f69df64ac7571da850696e0 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 31 Oct 2016 13:00:52 -0600 Subject: [PATCH 047/158] Migrated the read service to series service, and updates on the addPoint Removed the read service. The methods are now found in sereis service. Updating columns and content found in addpoint form --- odmtools/controller/logicCellEdit.py | 32 ++++++---- odmtools/controller/olvAddPoint.py | 17 +++--- odmtools/odmservices/ReadService.py | 80 ------------------------- odmtools/odmservices/series_service.py | 72 +++++++++++++++++++--- odmtools/odmservices/service_manager.py | 8 +-- 5 files changed, 92 insertions(+), 117 deletions(-) delete mode 100644 odmtools/odmservices/ReadService.py diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index fc063c1..6064691 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -21,9 +21,9 @@ class CellEdit(): def __init__(self, parent, serviceManager, recordService): self.parent = parent self.recordService = recordService - if serviceManager: - self.serviceManager = serviceManager - self.cv_service = serviceManager.get_cv_service() + self.serviceManager = serviceManager + if self.serviceManager: + self.cv_service = self.serviceManager.get_cv_service() self.series_service = serviceManager.get_series_service() offsetChoices = OrderedDict((x.description, x.id) for x in self.cv_service.get_offset_type_cvs()) @@ -44,6 +44,18 @@ def __init__(self, parent, serviceManager, recordService): self.offSetTypeChoices = [NULL] + ['SampleOffsetType1'] + ['SampleOffsetType2'] + ['SampleOffsetType3'] self.qualifierCodeChoices = [NULL] + ['SampleQualifierCode1'] + ['SampleQualifierCode2'] + ['SampleQualifierCode3'] + self.qualityCodeChoices = self.fetchQualityCodeChoices() + self.timeAggregationInterval = -1 + + def fetchQualityCodeChoices(self): + """ + :return: type(list + """ + if not self.serviceManager: + return [NULL] + + self.serviceManager.get_cv_service() + """ -------------------- Custom Image Getters @@ -334,16 +346,7 @@ def dateEditor(self, olv, rowIndex, subItemIndex): return odcb def offSetTypeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - odcb = CustomComboBox(olv, choices=self.offSetTypeChoices, style=wx.CB_READONLY) - # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb @@ -417,7 +420,10 @@ def valueDateTimeEditor(self, olv, rowIndex, subItemIndex): odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb - + def setComboForQualityCodeColumn(self, olv, rowIndex, subItemIndex): + odcb = CustomComboBox(olv, choices=self.qualityCodeChoices, style=wx.CB_READONLY) + odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return odcb class DatePicker(wx.DatePickerCtrl): diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index f942377..4e0ad97 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -33,6 +33,8 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.censorCode = censorCode self.qualifierCode = qualifierCode self.labSampleCode = labSampleCode + self.qualityCodeCV = "NULL" + self.timeAggInterval = -1 ## determines whether a row is in correct format or now self.validDataValue = False @@ -97,7 +99,7 @@ def __init__(self, *args, **kwargs): self.qualifierCodeEditor = cellEdit.qualifierCodeEditor self.labSampleEditor = cellEdit.labSampleCodeEditor self.valueDateTimeEditorCreator = cellEdit.valueDateTimeEditor - # self.valueDateTimeUTFOffset = cellEdit.valueDateTimeUTFOffsetCreator + self.qualityCodeCreator = cellEdit.setComboForQualityCodeColumn self.SetEmptyListMsg("Add points either by csv or by adding a new row") self.AddNamedImages("error", x_mark_16.GetBitmap(), x_mark_32.GetBitmap()) @@ -142,12 +144,12 @@ def buildOlv(self): ColumnDefn(title="ValueDateTimeUTFOffset", align="left", valueGetter="valueDateTimeUTFOffset", minimumWidth=130, headerImage="star"), - ColumnDefn("OffsetValue", "left", -1, valueGetter="offSetValue", minimumWidth=100, - stringConverter=self.offSetValue2Str, - imageGetter=self.imgGetterOffSetValue), - ColumnDefn("OffsetType", "left", -1, valueGetter="offSetType", minimumWidth=100, - imageGetter=self.imgGetterOffSetType, - cellEditorCreator=self.offSetTypeEditor), + ColumnDefn(title="Quality CodeCV", align="left", valueGetter="qualityCodeCV", + minimumWidth=130, cellEditorCreator=self.qualityCodeCreator, imageGetter="star"), + + ColumnDefn(title="TimeAggregationInterval", align="left", minimumWidth=130, + valueGetter="timeAggInterval", headerImage="star"), + ColumnDefn("QualifierCode", "left", -1, valueGetter="qualifierCode", minimumWidth=130, imageGetter=self.imgGetterQualifier, cellEditorCreator=self.qualifierCodeEditor), @@ -159,7 +161,6 @@ def buildOlv(self): self.SetColumns(columns) - # self.CreateCheckStateColumn() self.SetObjects(None) def rowFormatter(listItem, point): diff --git a/odmtools/odmservices/ReadService.py b/odmtools/odmservices/ReadService.py deleted file mode 100644 index e57dc2e..0000000 --- a/odmtools/odmservices/ReadService.py +++ /dev/null @@ -1,80 +0,0 @@ -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import VerticalDatumCV -from odmtools.odmdata import ValueTypeCV -from odmtools.odmdata import GeneralCategoryCV -from odmtools.odmdata import Unit -from sqlalchemy import not_ -from odm2api.ODM2.services.readService import ReadODM2 - - -class ReadService: - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.getSession() - self._debug = debug - self.read_service = ReadODM2(self._session_factory, debug=self._debug) - - # Controlled Vocabulary get methods - #return a list of all terms in the cv - def get_vertical_datum_cvs(self): - return self.read_service.getCVs(type="Elevation Datum") - - def get_samples(self): - return self.read_service.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) - - def get_site_type_cvs(self): - return self.read_service.getCVs(type="Site Type") # OR return self.read_service.getCVs(type="Sampling Feature Type") - - def get_variable_name_cvs(self): - return self.read_service.getCVs(type="Variable Name") - - def get_offset_type_cvs(self): - return self.read_service.getCVs(type="Spatial Offset Type") - - def get_speciation_cvs(self): - return self.read_service.getCVs(type="Speciation") - - def get_sample_medium_cvs(self): - return self.read_service.getCVs(type="Medium") - - def get_value_type_cvs(self): - result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() - return result - - def get_data_type_cvs(self): - return self.read_service.getCVs(type="dataset type") - - def get_general_category_cvs(self): - return self.read_service.getAnnotations(type="categoricalresultvalue") - - def get_censor_code_cvs(self): - return self.read_service.getCVs(type="censorcode") - - def get_sample_type_cvs(self): - return self.read_service.getCVs(type="Sampling Feature Type") - - def get_units(self): - return self.read_service.getUnits(ids=None, name=None, type=None) - - def get_units_not_uni(self): - result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() - return result - - def get_units_names(self): - result = self._edit_session.query(Unit.name).all() - return result - - # return a single cv - def get_unit_by_name(self, unit_name): - return self.read_service.getUnits(name=unit_name) - - def get_unit_by_id(self, unit_id): - return self.read_service.getUnits(ids=unit_id) - - def get_annotation_by_code(self, code): - return self.read_service.getAnnotations(type=code) - - def get_all_annotations(self): - return self.read_service.getAnnotations(type=None) - diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 8e3fc5d..7ef6afb 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,20 +1,15 @@ - import logging - - +from sqlalchemy import not_ from sqlalchemy import distinct, func - from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * from odmtools.common.logger import LoggerTool import pandas as pd - - logger =logging.getLogger('main') -class SeriesService(serviceBase): +class SeriesService(serviceBase): # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg def __init__(self, connection, debug=False): @@ -218,7 +213,7 @@ def get_unit_by_name(self, unit_name): # return self._edit_session.query(Unit).filter_by(name=unit_name).first() # except: # return None - return self.read.getUnits(name = [unit_name])[0] + return self.read.getUnits(name=[unit_name])[0] # def get_unit_by_id(self, unit_id): """ @@ -230,7 +225,7 @@ def get_unit_by_id(self, unit_id): # return self._edit_session.query(Unit).filter_by(id=unit_id).first() # except: # return None - return self.read.getUnits(ids = [unit_id])[0] + return self.read.getUnits(ids=[unit_id])[0] # def get_all_qualifiers(self): @@ -900,3 +895,62 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non # return result # except: # return None + + def get_vertical_datum_cvs(self): + return self.read.getCVs(type="Elevation Datum") + + def get_samples(self): + return self.read.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) + + def get_site_type_cvs(self): + return self.read.getCVs( + type="Site Type") # OR return self.read.getCVs(type="Sampling Feature Type") + + def get_variable_name_cvs(self): + return self.read.getCVs(type="Variable Name") + + def get_offset_type_cvs(self): + return self.read.getCVs(type="Spatial Offset Type") + + def get_speciation_cvs(self): + return self.read.getCVs(type="Speciation") + + def get_sample_medium_cvs(self): + return self.read.getCVs(type="Medium") + + def get_value_type_cvs(self): + return self.read.getCVs(type="Result Type") + + def get_data_type_cvs(self): + return self.read.getCVs(type="dataset type") + + def get_general_category_cvs(self): + return self.read.getAnnotations(type="categoricalresultvalue") + + def get_censor_code_cvs(self): + return self.read.getCVs(type="censorcode") + + def get_sample_type_cvs(self): + return self.read.getCVs(type="Sampling Feature Type") + + def get_units(self): + return self.read.getUnits(ids=None, name=None, type=None) + + def get_units_not_uni(self): + result = self._session.query(Units).filter(not_(Units.name.contains('angstrom'))).all() + return result + + def get_units_names(self): + result = self._session.query(Units.name).all() + return result + + def get_quality_code(self): + return self.read.getCVs(type="Quality Code") + + + def get_annotation_by_code(self, code): + return self.read.getAnnotations(type=code) + + + def get_all_annotations(self): + return self.read.getAnnotations(type=None) \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 72462ef..cff2cc8 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -125,7 +125,6 @@ def is_valid_connection(self): def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] - def get_series_service(self, conn_dict=None, conn_string=""): if not conn_dict: conn_dict = self.get_current_conn_dict() @@ -152,15 +151,10 @@ def get_series_service(self, conn_dict=None, conn_string=""): ss= SeriesService(conn) return ss - def get_cv_service(self): - conn_string = self._build_connection_string(self._current_conn_dict) - return ReadService(conn_string, self.debug) - - def get_edit_service(self, series_id, connection): return EditService(series_id, connection=connection, debug=self.debug) - + # todo: Not using build_connection_string. Need to update this def get_record_service(self, script, series_id, connection): return EditTools(self, script, self.get_edit_service(series_id, connection), self._build_connection_string(self.is_valid_connection())) From b840d6a3468b69d5e9ae668eaee55a24066f6bf5 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 31 Oct 2016 14:37:38 -0600 Subject: [PATCH 048/158] Cleaned the add point form and add all required columns Fixed the issue of the columns not resizing correctly. Removed unused code from the transition --- odmtools/controller/frmAddPoints.py | 5 + odmtools/controller/logicCellEdit.py | 140 +++++------------------- odmtools/controller/olvAddPoint.py | 38 +++---- odmtools/odmservices/series_service.py | 7 +- odmtools/odmservices/service_manager.py | 2 +- 5 files changed, 58 insertions(+), 134 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index b254734..bb9eb63 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -32,8 +32,13 @@ def onAddBtn(self, event): """ self.checkIfEditing() self.olv.AddObject(self.olv.sampleRow()) + self.auto_size_table() event.Skip() + def auto_size_table(self): + for i in range(self.olv.GetColumnCount()): + self.olv.SetColumnWidth(col=i, width=wx.LIST_AUTOSIZE_USEHEADER) + def onClearAllBtn(self, event): """ diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 6064691..7ff464d 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -23,17 +23,7 @@ def __init__(self, parent, serviceManager, recordService): self.recordService = recordService self.serviceManager = serviceManager if self.serviceManager: - self.cv_service = self.serviceManager.get_cv_service() - self.series_service = serviceManager.get_series_service() - offsetChoices = OrderedDict((x.description, x.id) for x in - self.cv_service.get_offset_type_cvs()) - self.offSetTypeChoices = [NULL] + offsetChoices.keys() - - labChoices = OrderedDict((x.lab_sample_code, x.id) for x in self.cv_service.get_samples()) - - self.censorCodeChoices = [NULL] + [x.term for x in self.cv_service.get_censor_code_cvs()] - self.labSampleChoices = [NULL] + labChoices.keys() - + self.series_service = self.serviceManager.get_series_service() self.qualifierChoices = OrderedDict((x.code + ':' + x.description, x.id) for x in self.series_service.get_all_qualifiers() if x.code and x.description) self.qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] @@ -45,7 +35,17 @@ def __init__(self, parent, serviceManager, recordService): self.qualifierCodeChoices = [NULL] + ['SampleQualifierCode1'] + ['SampleQualifierCode2'] + ['SampleQualifierCode3'] self.qualityCodeChoices = self.fetchQualityCodeChoices() + self.censorCodeChoices = self.fetchCensorCodeChoices() self.timeAggregationInterval = -1 + self.timeAggretaionUnitChoices = self.fetchTimeUnitChoices() + self.annotationChoices = [NULL] + + def fetchCensorCodeChoices(self): + if not self.serviceManager: + return [NULL] + + series_service = self.serviceManager.get_series_service() + return [NULL] + [x.Term for x in series_service.get_censor_code_cvs()] def fetchQualityCodeChoices(self): """ @@ -54,7 +54,14 @@ def fetchQualityCodeChoices(self): if not self.serviceManager: return [NULL] - self.serviceManager.get_cv_service() + series_service = self.serviceManager.get_series_service() + return [NULL] + [x.Term for x in series_service.get_quality_code()] + + def fetchTimeUnitChoices(self): + if not self.serviceManager: + return [NULL] + a = self.series_service.read.getCVs("aggregationstatistic") + return [NULL] + [x.Term for x in self.series_service.get_aggregation_statistic()] """ -------------------- @@ -239,16 +246,6 @@ def imgGetterQualifierCode(self, point): point.validQualifierCode = True return "check" - def imgGetterLabSampleCode(self, point): - """ - """ - - point.validLabSampleCode = False - if not point.labSampleCode in self.labSampleChoices: - return "error" - point.validLabSampleCode = True - return "check" - """ -------------------- Custom Value Setters @@ -281,32 +278,18 @@ def valueSetterUTCOffset(self, point, newValue): ------------------------ """ def strConverterDataValue(self, value): - """ - """ - try: return str(value) except Exception as e: return str(NULL) def strConverterLocalTime(self, time): - """Required Element - - :param time: - :return: - """ - return unicode(time) def strConverterUTCOffset(self, value): - """ - """ - return str(value) def strConverterOffSetValue(self, value): - """ - """ try: return str(value) except UnicodeEncodeError: @@ -319,28 +302,12 @@ def strConverterOffSetValue(self, value): """ def localTimeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - - # odcb = masked.TimeCtrl(olv, fmt24hr=True) odcb = TimePicker(olv) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb def dateEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ odcb = DatePicker(olv) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb @@ -350,71 +317,12 @@ def offSetTypeEditor(self, olv, rowIndex, subItemIndex): odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb - def qualifierCodeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - def cbHandler(event): - """ - :param event: - :type wx.EVT_COMBOBOX: - """ - - if event.GetEventObject().Value == NEW: - dlg = frmFlagValues(self.parent, self.cv_service, self.qualifierChoices, isNew=True) - - value = dlg.ShowModal() - if value == wx.ID_OK and dlg.selectedValue: - self.qualifierCodeChoices.insert(0, dlg.selectedValue) - event.GetEventObject().SetItems(self.qualifierCodeChoices) - print event.GetEventObject().GetValue() - print type(event.GetEventObject()) - event.GetEventObject().SetValue(dlg.selectedValue) - print event.GetEventObject().GetValue() - #dlg.Destroy() - - try: - self.qualifierChoices = OrderedDict((x.code + ':' + x.description, x.id) - for x in self.cv_service.get_all_qualifiers() if x.code and x.description) - self.qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] - except: - pass - odcb = CustomComboBox(olv, choices=self.qualifierCodeChoices, style=wx.CB_READONLY) - # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead - odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) - odcb.Bind(wx.EVT_COMBOBOX, cbHandler) - return odcb - def censorCodeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ odcb = CustomComboBox(olv, choices=self.censorCodeChoices, style=wx.CB_READONLY) # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb - def labSampleCodeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - - odcb = CustomComboBox(olv, choices=self.labSampleChoices, style=wx.CB_READONLY) - odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) - return odcb - def valueDateTimeEditor(self, olv, rowIndex, subItemIndex): odcb = DatePicker(olv) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) @@ -425,6 +333,16 @@ def setComboForQualityCodeColumn(self, olv, rowIndex, subItemIndex): odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb + def setComboForTimeAggregationUnitIDCreator(self, olv, rowIndex, subItemIndex): + customCombo = CustomComboBox(olv, choices=self.timeAggretaionUnitChoices, style=wx.CB_READONLY) + customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return customCombo + + def setComboForAnnotation(self, olv, rowIndex, subItemIndex): + customCombo = CustomComboBox(olv, choices=self.annotationChoices, style=wx.CB_READONLY) + customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return customCombo + class DatePicker(wx.DatePickerCtrl): """ diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 4e0ad97..178eed8 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -4,13 +4,13 @@ from odmtools.common.icons import x_mark_16, star_16, star_32, x_mark_32, check_mark_3_16, check_mark_3_32 from odmtools.controller.logicCellEdit import CellEdit, NULL from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn +from odmtools.odmservices.service_manager import ServiceManager OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() class Points(object): def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, - censorCode="NULL", valueAccuracy="NULL", offSetValue="NULL", offSetType="NULL", qualifierCode="NULL", - labSampleCode="NULL"): + censorCode="NULL", valueAccuracy="NULL", offSetValue="NULL"): try: self.dataValue = str(dataValue) except: @@ -29,12 +29,11 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.utcOffSet = str(utcOffSet) self.valueDateTimeUTFOffset = -1 self.offSetValue = offSetValue - self.offSetType = offSetType self.censorCode = censorCode - self.qualifierCode = qualifierCode - self.labSampleCode = labSampleCode self.qualityCodeCV = "NULL" self.timeAggInterval = -1 + self.timeAggregationUnitID = "NULL" + self.annotation = "NULL" ## determines whether a row is in correct format or now self.validDataValue = False @@ -51,10 +50,14 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 class OLVAddPoint(FastObjectListView): def __init__(self, *args, **kwargs): - try: + if "serviceManager" in kwargs: self.serviceManager = kwargs.pop("serviceManager") - except: - self.serviceManager = None + else: + try: + self.serviceManager = ServiceManager() + except: + self.serviceManager = None + try: self.recordService = kwargs.pop("recordService") except: @@ -73,7 +76,6 @@ def __init__(self, *args, **kwargs): self.imgGetterCensorCode = cellEdit.imgGetterCensorCode self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset self.imgGetterValueAcc = cellEdit.imgGetterValueAcc - self.imgGetterlabSample = cellEdit.imgGetterLabSampleCode self.imgGetterQualifier = cellEdit.imgGetterQualifierCode self.imgGetterOffSetType = cellEdit.imgGetterOffSetType self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue @@ -95,11 +97,10 @@ def __init__(self, *args, **kwargs): self.dateEditor = cellEdit.dateEditor self.timeEditor = cellEdit.localTimeEditor self.censorEditor = cellEdit.censorCodeEditor - self.offSetTypeEditor = cellEdit.offSetTypeEditor - self.qualifierCodeEditor = cellEdit.qualifierCodeEditor - self.labSampleEditor = cellEdit.labSampleCodeEditor self.valueDateTimeEditorCreator = cellEdit.valueDateTimeEditor self.qualityCodeCreator = cellEdit.setComboForQualityCodeColumn + self.timeAggregationUnitIDCreator = cellEdit.setComboForTimeAggregationUnitIDCreator + self.annotationCreator = cellEdit.setComboForAnnotation self.SetEmptyListMsg("Add points either by csv or by adding a new row") self.AddNamedImages("error", x_mark_16.GetBitmap(), x_mark_32.GetBitmap()) @@ -150,13 +151,12 @@ def buildOlv(self): ColumnDefn(title="TimeAggregationInterval", align="left", minimumWidth=130, valueGetter="timeAggInterval", headerImage="star"), - ColumnDefn("QualifierCode", "left", -1, valueGetter="qualifierCode", minimumWidth=130, - imageGetter=self.imgGetterQualifier, - cellEditorCreator=self.qualifierCodeEditor), - ColumnDefn("LabSampleCode", "left", -1, valueGetter="labSampleCode", minimumWidth=130, - imageGetter=self.imgGetterlabSample, - cellEditorCreator=self.labSampleEditor - ), + ColumnDefn(title="TimeAggregationUnitID", align="left", minimumWidth=130, + valueGetter="timeAggregationUnitID", cellEditorCreator=self.timeAggregationUnitIDCreator, headerImage="star"), + + ColumnDefn(title="Annotation", align="left", minimumWidth=130, + valueGetter="annotation", cellEditorCreator=self.annotationCreator, headerImage="star") + ] self.SetColumns(columns) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 7ef6afb..3bae7d9 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -947,10 +947,11 @@ def get_units_names(self): def get_quality_code(self): return self.read.getCVs(type="Quality Code") - def get_annotation_by_code(self, code): return self.read.getAnnotations(type=code) - def get_all_annotations(self): - return self.read.getAnnotations(type=None) \ No newline at end of file + return self.read.getAnnotations(type=None) + + def get_aggregation_statistic(self): + return self.read.getCVs(type="aggregationstatistic") \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index cff2cc8..663b094 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -22,7 +22,7 @@ class ServiceManager(): - def __init__(self, debug=False, conn_dict = None): + def __init__(self, debug=False, conn_dict=None): self.debug = debug f = self._get_file('r') self._conn_dicts = [] From 50de11861f319becdcc72d71d277dd93670a72ab Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 1 Nov 2016 12:11:08 -0600 Subject: [PATCH 049/158] removed unnecessery api calls --- odmtools/odmservices/series_service.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 3bae7d9..ae6fbae 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -294,15 +294,6 @@ def get_method_by_description(self, method_code): # logger.error("method not found") # return result # -# def get_offset_types_by_series_id(self, series_id): -# """ -# -# :param series_id: -# :return: -# """ -# subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( -# Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() -# return self._edit_session.query(OffsetType).join(subquery).distinct().all() # # def get_samples_by_series_id(self, series_id): # """ From 8b973c5db6dd1e6f9a1bce521cd4aa9d6abba4dc Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 1 Nov 2016 12:30:26 -0600 Subject: [PATCH 050/158] series get adds --- odmtools/odmservices/series_service.py | 60 ++++++++++++++------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index ae6fbae..891058d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -306,38 +306,42 @@ def get_method_by_description(self, method_code): # return self._edit_session.query(Sample).join(subquery).distinct().all() # # # Series Catalog methods -# def get_all_series(self): -# """ -# Returns all series as a modelObject -# :return: List[Series] -# """ + def get_all_series(self): + """ + Returns all series as a modelObject + :return: List[Series] + """ + return self.read.getResults(type="timeSeries") # # #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) # return self._edit_session.query(Series).order_by(Series.id).all() # -# def get_series_by_site(self , site_id): -# """ -# -# :param site_id: int -# :return: List[Series] -# """ -# try: -# selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() -# return selectedSeries -# except: -# return None -# -# def get_series_by_id(self, series_id): -# """ -# -# :param series_id: int -# :return: Series -# """ -# try: -# return self._edit_session.query(Series).filter_by(id=series_id).first() -# except Exception as e: -# print e -# return None + def get_series_by_site(self , site_id): + """ + + :param site_id: int + :return: List[Series] + # """ + # try: + # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() + # return selectedSeries + # except: + # return None + return self.read.getResults(type="timeSeries", ids=[site_id])[0] + + + def get_series_by_id(self, series_id): + """ + + :param series_id: int + :return: Series + """ + # try: + # return self._edit_session.query(Series).filter_by(id=series_id).first() + # except Exception as e: + # print e + # return None + return self.read.getResults(type="timeSeries", ids=[series_id])[0] # # def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): # """ From a9185ed36c0283a096b503f884f65bdf85627936 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 1 Nov 2016 15:02:54 -0600 Subject: [PATCH 051/158] update setup file --- odmtools/odmdata/memory_database.py | 2 +- odmtools/odmservices/edit_service.py | 4 +-- odmtools/odmservices/series_service.py | 38 +++++++++++++++++++++++-- odmtools/odmservices/service_manager.py | 2 +- setup/Windows/odmtools_setup_build.iss | 4 +-- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 5cf4e76..d0f91ac 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -214,7 +214,7 @@ def initEditValues(self, seriesID): logger.debug("Load series from db") self.series = self.series_service.get_series_by_id(seriesID) - self.df = self.series_service.get_values_by_series(seriesID) + self.df = self.series_service.get_values(series_id= seriesID) self.editLoaded = True diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 2c45e6d..8c3bcb9 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -423,8 +423,8 @@ def drift_correction(self, gap_width): # f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) # tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) - f = lambda row : row["DataValue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) if row["DataValue"] != nodv else row["DataValue"] - tmp_filter_list["DataValue"]=tmp_filter_list.apply(f, axis = 1) + f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) if row["datavalue"] != nodv else row["datavalue"] + tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) update_list = [{"value": row["datavalue"], "id":index} for index, row in tmp_filter_list.iterrows()] diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 891058d..6f28ff8 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -116,10 +116,43 @@ def get_variables_by_site_code(self, site_code): q = self._session.query(Variables).filter(Variables.VariableID.in_(var_ids)) return q.all() + # Data Value Methods + def get_values(self, series_id=None): + ''' + + :param series_id: Series id + :return: pandas dataframe + ''' + # series= self.get_series_by_id(series_id) + # if series: + # q = self._edit_session.query(DataValue).filter_by( + # site_id=series.site_id, + # variable_id=series.variable_id, + # method_id=series.method_id, + # source_id=series.source_id, + # quality_control_level_id=series.quality_control_level_id) + # + # query=q.statement.compile(dialect=self._session_factory.engine.dialect) + # data= pd.read_sql_query(sql= query, + # con = self._session_factory.engine, + # params = query.params ) + # #return data.set_index(data['LocalDateTime']) + # return data + # else: + # return None + + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, + con=self._session_factory.engine, + params=query.params) + data.set_index(data['valuedatetime'], inplace=True) + return data # Series Catalog methods - - def get_series_by_site(self , site_id): """ :param site_id: int @@ -161,7 +194,6 @@ def get_site_by_id(self, site_id): return self.read.getSampling(ids = [site_id])[0] # - # def get_all_variables(self): """ diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 663b094..8765b08 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -131,7 +131,7 @@ def get_series_service(self, conn_dict=None, conn_string=""): if conn_string: #todo how to get version from a connection string - conn = dbconnection.createConnectionFromString(conn_string, float(self.get_current_conn_dict()["version"])) + conn = dbconnection.createConnectionFromString(conn_string, 2.0)#float(self.get_current_conn_dict()["version"])) else: conn = dbconnection.createConnection(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], conn_dict['password'], conn_dict['version']) diff --git a/setup/Windows/odmtools_setup_build.iss b/setup/Windows/odmtools_setup_build.iss index cd72382..2f2e601 100644 --- a/setup/Windows/odmtools_setup_build.iss +++ b/setup/Windows/odmtools_setup_build.iss @@ -23,7 +23,7 @@ AppUpdatesURL={#MyAppURL} DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} OutputBaseFilename={#MyAppName}_{#MyAppVersion}_Installer -SetupIconFile={#MyAppDir}\odmtools\common\icons\ODMTools.ico +SetupIconFile=D:\DEV\Releases\ODMTools\odmtools_beta_source_code\odmtools\common\icons\ODMTools.ico Compression=lzma SolidCompression=yes @@ -40,7 +40,7 @@ Source: "D:\DEV\Releases\ODMTools\*"; DestDir: "{app}"; Flags: ignoreversion rec [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\run.bat"; WorkingDir: "{app}"; Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\run.bat"; Tasks: desktopicon [Dirs] Name: "{localappdata}\{#OrgName}\{#MyAppName}" From 39a89296f570c21fd014d3bd60a7414c4dabd5d6 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 1 Nov 2016 16:51:11 -0600 Subject: [PATCH 052/158] re-add export_service --- odmtools/odmservices/export_service.py | 401 +++++++++++++++++++++++++ odmtools/odmservices/series_service.py | 12 +- 2 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 odmtools/odmservices/export_service.py diff --git a/odmtools/odmservices/export_service.py b/odmtools/odmservices/export_service.py new file mode 100644 index 0000000..4b6d1eb --- /dev/null +++ b/odmtools/odmservices/export_service.py @@ -0,0 +1,401 @@ +import csv +import xml.etree.cElementTree as ET +import datetime + + +class ExportService(): + ''' + Create with the Service Manager!!! + ''' + + def __init__(self, series_service): + self._series_service = series_service + self.dt_format_str = "%m/%d/%Y %I:%M:%S %p" + + def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, + src=False, qcl=False): + series = self._series_service.get_series_by_id(series_id) + if series is None: + return False + + writer = csv.writer(open(filename, 'wb')) + print "filename: ", filename + self.write_data_header(writer, utc, site, var, offset, qual, src, qcl) + for dv in series.data_values: + self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) + + def write_data_row(self, writer, series, dv, utc, site, var, offset, qual, src, qcl): + data = [] + data.append(series.id) + data.append(dv.id) + data.append(dv.data_value) + data.append(dv.value_accuracy) + data.append(dv.local_date_time) + if utc: + data.append(dv.utc_offset) + data.append(dv.date_time_utc) + data.append(series.site_code) + if site: + data.append(series.site_name) + data.append(series.site.type) + data.append(series.site.latitude) + data.append(series.site.longitude) + data.append(series.site.spatial_ref.srs_name) + data.append(series.variable_code) + if var: + data.append(series.variable_name) + data.append(series.speciation) + data.append(series.variable_units_name) + data.append(series.variable.variable_unit.abbreviation) + data.append(series.sample_medium) + data.append(dv.offset_value) + data.append(dv.offset_type_id) + if offset: + if dv.offset_type is not None: + data.append(dv.offset_type.description) + data.append(dv.offset_type.unit.name) + else: + data.append('') + data.append('') + data.append(dv.censor_code) + data.append(dv.qualifier_id) + if qual: + if dv.qualifier is not None: + data.append(dv.qualifier.code) + data.append(dv.qualifier.description) + else: + data.append('') + data.append('') + if src: + data.append(series.organization) + data.append(series.source_description) + data.append(series.citation) + if qcl: + data.append(series.quality_control_level_code) + data.append(series.quality_control_level.definition) + data.append(series.quality_control_level.explanation) + data.append(dv.sample_id) + + writer.writerow(data) + + def write_data_header(self, writer, utc, site, var, offset, qual, src, qcl): + # Build header list + header = [] + header.append("SeriesId") + header.append("ValueId") + header.append("DataValue") + header.append("ValueAccuracy") + header.append("LocalDateTime") + if utc: + header.append("UTCOffset") + header.append("DateTimeUTC") + header.append("SiteCode") + if site: + header.append("SiteName") + header.append("SiteType") + header.append("Latitude") + header.append("Longitude") + header.append("SRSName") + header.append("VariableCode") + if var: + header.append("VariableName") + header.append("Speciation") + header.append("VariableUnitsName") + header.append("VariableUnitsAbbreviation") + header.append("SampleMedium") + header.append("OffsetValue") + header.append("OffsetTypeID") + if offset: + header.append("OffsetDescription") + header.append("OffsetUnitsName") + header.append("CensorCode") + header.append("QualifierID") + if qual: + header.append("QualifierCode") + header.append("QualifierDescription") + if src: + header.append("Organization") + header.append("SourceDescription") + header.append("Citation") + if qcl: + header.append("QualityControlLevelCode") + header.append("Definition") + header.append("Explanation") + header.append("SampleID") + + writer.writerow(header) + + def export_series_metadata(self, series_ids, filename): + if series_ids is None: + return + + root = ET.Element("Metadata") + list_root = ET.SubElement(root, "DataSeriesList") + list_root.set("Total", str(series_ids)) + + try: + with open(filename): + file_exists = True + except IOError: + file_exists = False + + if file_exists: + # Read the file into the XML tree + pass + + if isinstance(series_ids, int): + series = self._series_service.get_series_by_id(series_ids) + self.append_series_node(series, list_root) + else: + for series_id in series_ids: + series = self._series_service.get_series_by_id(series_id) + self.append_series_node(series, list_root) + + tree = ET.ElementTree(root) + tree.write(filename) + + def append_series_node(self, series, parent): + series_node = ET.SubElement(parent, "DataSeries") + series_node.set("ID", str(series.id)) + self.append_general_info(series, series_node) + self.append_site_info(series, series_node) + self.append_var_info(series, series_node) + self.append_method_source_info(series, series_node) + self.append_misc_info(series, series_node) + + return series_node + + def append_general_info(self, series, parent): + meta = series.source.iso_metadata + general_node = ET.SubElement(parent, "GeneralInformation") + topic = ET.SubElement(general_node, "TopicCategory") + topic.text = meta.topic_category + title = ET.SubElement(general_node, "Title") + title.text = meta.title + abstract = ET.SubElement(general_node, "Abstract") + abstract.text = meta.abstract + prof_version = ET.SubElement(general_node, "ProfileVersion") + prof_version.text = meta.profile_version + metadata_link = ET.SubElement(general_node, "MetadataLink") + metadata_link.text = meta.metadata_link + date = ET.SubElement(general_node, "MetadataCreationDate") + # 7/1/2013 12:17:16 PM + date.text = datetime.datetime.now().strftime(self.dt_format_str) + + def append_site_info(self, series, parent): + site = series.site + site_node = ET.SubElement(parent, "SiteInformation") + site_code = ET.SubElement(site_node, "SiteCode") + site_code.text = site.code + site_name = ET.SubElement(site_node, "SiteName") + site_name.text = site.name + site_type = ET.SubElement(site_node, "SiteType") + site_type.text = site.type + + geo_coords = ET.SubElement(site_node, "GeographicCoordinates") + latitude = ET.SubElement(geo_coords, "Latitude") + latitude.text = str(site.latitude) + longitude = ET.SubElement(geo_coords, "Longitude") + longitude.text = str(site.longitude) + srs_id = ET.SubElement(geo_coords, "SRSID") + srs_id.text = str(site.spatial_ref.srs_id) + srs_name = ET.SubElement(geo_coords, "SRSName") + srs_name.text = site.spatial_ref.srs_name + is_geo = ET.SubElement(geo_coords, "IsGeographic") + is_geo.text = str(site.spatial_ref.is_geographic) + notes = ET.SubElement(geo_coords, "Notes") + notes.text = site.spatial_ref.notes + + local_coords = ET.SubElement(site_node, "LocalCoordinates") + local_x = ET.SubElement(local_coords, "LocalX") + local_x.text = str(site.local_x) + local_y = ET.SubElement(local_coords, "LocalY") + local_y.text = str(site.local_y) + local_srs_id = ET.SubElement(local_coords, "SRSID") + local_srs_id.text = str(site.local_spatial_ref.srs_id) + local_srs_name = ET.SubElement(local_coords, "SRSName") + local_srs_name.text = site.local_spatial_ref.srs_name + local_is_geo = ET.SubElement(local_coords, "IsGeographic") + local_is_geo.text = str(site.local_spatial_ref.is_geographic) + local_notes = ET.SubElement(local_coords, "Notes") + local_notes.text = site.local_spatial_ref.notes + elevation = ET.SubElement(local_coords, "Elevation_m") + if site.elevation_m: elevation.text = str(site.elevation_m) + vert_datum = ET.SubElement(local_coords, "VerticalDatum") + if site.vertical_datum_id: vert_datum.text = str(site.vertical_datum_id) + + pos_accuracy = ET.SubElement(site_node, "PosAccuracy_m") + pos_accuracy.text = str(site.pos_accuracy_m) + state = ET.SubElement(site_node, "State") + state.text = site.state + county = ET.SubElement(site_node, "County") + county.text = site.county + comments = ET.SubElement(site_node, "Comments") + comments.text = site.comments + + def append_var_info(self, series, parent): + variable = series.variable + var_node = ET.SubElement(parent, "VariableInformation") + + var_code = ET.SubElement(var_node, "VariableCode") + var_code.text = variable.code + var_name = ET.SubElement(var_node, "VariableName") + var_name.text = variable.name + speciation = ET.SubElement(var_node, "Speciation") + speciation.text = variable.speciation + + var_units = ET.SubElement(var_node, "VariableUnits") + units_name = ET.SubElement(var_units, "UnitsName") + units_name.text = variable.variable_unit.name + units_type = ET.SubElement(var_units, "UnitsType") + units_type.text = variable.variable_unit.type + units_abbrev = ET.SubElement(var_units, "UnitsAbbreviation") + units_abbrev.text = variable.variable_unit.abbreviation + + sample_medium = ET.SubElement(var_node, "SampleMedium") + sample_medium.text = variable.sample_medium + val_type = ET.SubElement(var_node, "ValueType") + val_type.text = variable.value_type + is_reg = ET.SubElement(var_node, "IsRegular") + is_reg.text = str(variable.is_regular) + time_support = ET.SubElement(var_node, "TimeSupport") + time_support.text = str(variable.time_support) + + time_support_units = ET.SubElement(var_node, "TimeSupportUnits") + ts_units_name = ET.SubElement(time_support_units, "UnitsName") + ts_units_name.text = variable.time_unit.name + ts_units_type = ET.SubElement(time_support_units, "UnitsType") + ts_units_type.text = variable.time_unit.type + ts_units_abbrev = ET.SubElement(time_support_units, "UnitsAbbreviation") + ts_units_abbrev.text = variable.time_unit.abbreviation + + data_type = ET.SubElement(var_node, "DataType") + data_type.text = variable.data_type + gen_cat = ET.SubElement(var_node, "GeneralCategory") + gen_cat.text = variable.general_category + no_dv = ET.SubElement(var_node, "NoDataValue") + no_dv.text = str(variable.no_data_value) + + period = ET.SubElement(var_node, "PeriodOfRecord") + begin_dt = ET.SubElement(period, "BeginDateTime") + begin_dt.text = series.begin_date_time.strftime(self.dt_format_str) + end_dt = ET.SubElement(period, "EndDateTime") + end_dt.text = series.end_date_time.strftime(self.dt_format_str) + begin_dt_utc = ET.SubElement(period, "BeginDateTimeUTC") + begin_dt_utc.text = series.begin_date_time_utc.strftime(self.dt_format_str) + end_dt_utc = ET.SubElement(period, "EndDateTimeUTC") + end_dt_utc.text = series.end_date_time_utc.strftime(self.dt_format_str) + value_count = ET.SubElement(period, "ValueCount") + value_count.text = str(series.value_count) + + def append_method_source_info(self, series, parent): + method = series.method + method_node = ET.SubElement(parent, "MethodInformation") + method_desc = ET.SubElement(method_node, "MethodDescription") + method_desc.text = method.description + method_link = ET.SubElement(method_node, "MethodLink") + method_link.text = method.link + + source = series.source + source_node = ET.SubElement(parent, "SourceInformation") + org = ET.SubElement(source_node, "Organization") + org.text = source.organization + source_desc = ET.SubElement(source_node, "SourceDescription") + source_desc.text = source.description + source_link = ET.SubElement(source_node, "SourceLink") + source_link.text = source.link + + contact = ET.SubElement(source_node, "Contact") + contact_name = ET.SubElement(contact, "ContactName") + contact_name.text = source.contact_name + phone = ET.SubElement(contact, "Phone") + phone.text = source.phone + email = ET.SubElement(contact, "Email") + email.text = source.email + address = ET.SubElement(contact, "Address") + address.text = source.address + city = ET.SubElement(contact, "City") + city.text = source.city + state = ET.SubElement(contact, "State") + state.text = source.state + zip_code = ET.SubElement(contact, "ZipCode") + zip_code.text = source.zip_code + + citation = ET.SubElement(source_node, "Citation") + citation.text = source.citation + + def append_misc_info(self, series, parent): + qcl = series.quality_control_level + + qcl_node = ET.SubElement(parent, "QualityControlLevelInformation") + qcl_code = ET.SubElement(qcl_node, "QualityControlLevelCode") + qcl_code.text = qcl.code + qcl_def = ET.SubElement(qcl_node, "Definition") + qcl_def.text = qcl.definition + qcl_expl = ET.SubElement(qcl_node, "Explanation") + qcl_expl.text = qcl.explanation + + offsets_node = ET.SubElement(parent, "OffsetInformation") + offsets = self._series_service.get_offset_types_by_series_id(series.id) + for offset in offsets: + offset_id = ET.SubElement(offsets_node, "Offset") + if offset: + offset_id.set("ID", str(offset.id)) + else: + offset_id.set("ID", "") + offset_desc = ET.SubElement(offsets_node, "OffsetDescription") + if offset: offset_desc.text = offset.description + offset_units = ET.SubElement(offsets_node, "OffsetUnits") + units_name = ET.SubElement(offset_units, "UnitsName") + if offset: units_name.text = offset.unit.name + units_type = ET.SubElement(offset_units, "UnitsType") + if offset: units_type.text = offset.unit.type + units_abbrev = ET.SubElement(offset_units, "UnitsAbbreviation") + if offset: units_abbrev.text = offset.unit.abbreviation + + qualifiers_node = ET.SubElement(parent, "QualifierInformation") + qualifiers = self._series_service.get_qualifiers_by_series_id(series.id) + for qual in qualifiers: + qual_id = ET.SubElement(qualifiers_node, "Qualifier") + if qual: + qual_id.set("ID", str(qual.id)) + else: + qual_id.set("ID", "") + qual_code = ET.SubElement(qual_id, "QualiferCode") + if qual: qual_code.text = qual.code + qual_desc = ET.SubElement(qual_id, "QualifierDescription") + if qual: qual_desc.text = qual.description + + samples_node = ET.SubElement(parent, "SampleInformation") + samples = self._series_service.get_samples_by_series_id(series.id) + for sample in samples: + sample_id = ET.SubElement(samples_node, "Sample") + if sample: + sample_id.set("ID", str(sample.id)) + else: + sample_id.set("ID", "") + sample_type = ET.SubElement(sample_id, "SampleType") + if sample: sample_type.text = sample.type + lab_code = ET.SubElement(sample_id, "LabSampleCode") + if sample: lab_code.text = sample.lab_sample_code + lab_method_id = ET.SubElement(sample_id, "LabMethodID") + if sample: lab_method_id = sample.lab_method_id + + lab_method_node = ET.SubElement(parent, "LabMethodInformation") + for sample in samples: + if sample: lab_method = sample.lab_method + lab_method_id = ET.SubElement(lab_method_node, "LabMethod") + if lab_method: + lab_method_id.set("ID", str(lab_method.id)) + else: + lab_method_id.set("ID", "") + lab_name = ET.SubElement(lab_method_id, "LabName") + if lab_method: lab_name.text = lab_method.name + lab_org = ET.SubElement(lab_method_id, "LabOrganization") + if lab_method: lab_org.text = lab_method.organization + method_name = ET.SubElement(lab_method_id, "LabMethodName") + if lab_method: method_name.text = lab_method.method_name + method_desc = ET.SubElement(lab_method_id, "LabMethodDescription") + if lab_method: method_desc.text = lab_method.method_description + method_link = ET.SubElement(lab_method_id, "LabMethodLink") + if lab_method: method_link.text = lab_method.link \ No newline at end of file diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 6f28ff8..f63add0 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -338,16 +338,10 @@ def get_method_by_description(self, method_code): # return self._edit_session.query(Sample).join(subquery).distinct().all() # # # Series Catalog methods - def get_all_series(self): - """ - Returns all series as a modelObject - :return: List[Series] - """ - return self.read.getResults(type="timeSeries") -# -# #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) -# return self._edit_session.query(Series).order_by(Series.id).all() + # + + #TODO siteid should actually be joined through featureaction and filtered that way def get_series_by_site(self , site_id): """ From 7e7af86995004be7d026ca6e97bec01bf4395d55 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 1 Nov 2016 16:55:33 -0600 Subject: [PATCH 053/158] re- add Fran's create functions - lost by a merge --- odmtools/odmservices/series_service.py | 111 +++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index f63add0..4891a51 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -917,6 +917,117 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non # except: # return None + + def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): + """ + series_service -> Result in ODM2 + :param data_values: + :param site_id: + :param variable_id: + :param method_id: + :param source_id: + :param qcl_id: + :return: + """ + self.update_dvs(data_values) + series = Series() + series.site_id = site_id + series.variable_id = variable_id + series.method_id = method_id + series.source_id = source_id + series.quality_control_level_id = qcl_id + + return self.create_service.createResult(series) + + def create_method(self, description, link): + """ + :param description: + :param link: + :return: + """ + method = Methods() + method.MethodDescription = description + if link is not None: + method.MethodLink = link + + return self.create_service.createMethod(method=method) + + def create_variable_by_var(self, var): + """ + :param var: Variable Object + :return: + """ + return self.create_service.createVariable(var=var) + + def create_variable( + self, code, name, speciation, variable_unit_id, sample_medium, + value_type, is_regular, time_support, time_unit_id, data_type, + general_category, no_data_value): + """ + :param code: + :param name: + :param speciation: + :param variable_unit_id: + :param sample_medium: + :param value_type: + :param is_regular: + :param time_support: + :param time_unit_id: + :param data_type: + :param general_category: + :param no_data_value: + :return: + """ + # var = Variable() + variable = Variables() + variable.VariableCode = code + variable.VariableNameCV = name + variable.SpeciationCV = speciation + # Commented lines indicate that Variables does not have such attributes + # var.variable_unit_id = variable_unit_id + # var.sample_medium = sample_medium + # var.value_type = value_type + # var.is_regular = is_regular + # var.time_support = time_support + # var.time_unit_id = time_unit_id + # var.data_type = data_type + # var.general_category = general_category + variable.NoDataValue = no_data_value + + return self.create.createVariable(var=variable) + + def create_processing_level(self, code, definition, explanation): + """ + qcl -> Processing Level in ODM2 + :param code: + :param definition: + :param explanation: + :return: + """ + procLevel = ProcessingLevels() + procLevel.ProcessingLevelCode = code + procLevel.Definition = definition + procLevel.Explanation = explanation + return self.create.createProcessingLevel(proclevel=procLevel) + + def create_annotation_by_anno(self, annotation): + return self.create.createAnnotations(annotation) + + def create_annotation(self, code, description): + """ + :param code: + :param description: + :return: + """ + annotation = Annotations() + annotation.AnnotationCode = code + annotation.AnnotationText = description + annotation.AnnotationTypeCV = "timeSeriesResultValueAnnotation" + + return self.create_annotation_by_anno(annotation) + + + def get_vertical_datum_cvs(self): return self.read.getCVs(type="Elevation Datum") From 08fedaec98b4cf7f770c5c7df2cee3424afedad8 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 1 Nov 2016 17:44:33 -0600 Subject: [PATCH 054/158] try to fix issues the merge causedg --- odmtools/common/taskServer.py | 7 ++----- odmtools/controller/logicPlotOptions.py | 9 ++++----- odmtools/odmdata/__init__.py | 1 - odmtools/odmdata/memory_database.py | 4 ++-- odmtools/odmservices/__init__.py | 13 ++++--------- odmtools/odmservices/edit_service.py | 4 ++-- odmtools/odmservices/series_service.py | 15 +-------------- odmtools/odmservices/service_manager.py | 4 +++- tests/test_odmdata/test_FreeTDS.py | 1 + 9 files changed, 19 insertions(+), 39 deletions(-) diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index ffaf6db..f1fde26 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -132,11 +132,8 @@ def worker(cls, dispatcher): connection = SeriesService("sqlite:///:memory:") df = task[1] logger.debug("Load series from db") - df.to_sql(name="DataValues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) -# ======= -# logger.debug("Load series_service from db") -# df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) -# >>>>>>> origin/update_cvs + df.to_sql(name="odm2.timeseriesresultvalues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) + logger.debug("done loading database") result = connection if task_type == "UpdateEditDF": diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 9de86da..1f634da 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -188,9 +188,9 @@ def update(self, key, isselected): def getUpdatedData(self, key): results = self.taskserver.getCompletedTasks() - self._seriesInfos[key].Probability = results['Probability'] self._seriesInfos[key].Statistics = results['Summary'] self._seriesInfos[key].BoxWhisker = results['BoxWhisker'] + self._seriesInfos[key].Probability = results['Probability'] def setBoxInterval(self, title): @@ -211,7 +211,7 @@ def getAllSeries(self): def getSeriesById(self, seriesID): try: - series = self.memDB.series_service.get_series_by_id(seriesID) + series = self.memDB.series_service.get_series(series=seriesID) self.memDB.series_service.reset_session() return series @@ -292,7 +292,7 @@ def getSeriesInfo(self, seriesID): series = self.getSeriesById(seriesID) if not series: - message = "Please check your database connection. Unable to retrieve series_service %d from the database" % seriesID + message = "Please check your database connection. Unable to retrieve series %d from the database" % seriesID wx.MessageBox(message, 'ODMTool Python', wx.OK | wx.ICON_EXCLAMATION) return @@ -336,7 +336,7 @@ def updateDateRange(self, startDate=None, endDate=None): self.currentStart = startDate self.currentEnd = endDate else: - #this returns the series_service to its full daterange + #this returns the series to its full daterange data = self.memDB.getDataValuesforGraph(key, seriesInfo.noDataValue, seriesInfo.startDate, seriesInfo.endDate) self.isSubsetted = False @@ -589,4 +589,3 @@ def numToSeason(date): - diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 743963e..9c64e27 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,6 +1,5 @@ -#from odm2api.ODM1_1_1.services import SeriesService#, refreshDB from odm2api.ODMconnection import SessionFactory, dbconnection from odm2api.ODM2.models import _changeSchema as change_schema diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index d0f91ac..acfed20 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -213,7 +213,7 @@ def initEditValues(self, seriesID): if not self.editLoaded: logger.debug("Load series from db") - self.series = self.series_service.get_series_by_id(seriesID) + self.series = self.series_service.get_series(seriesID) self.df = self.series_service.get_values(series_id= seriesID) self.editLoaded = True @@ -230,7 +230,7 @@ def initEditValues(self, seriesID): logger.debug("no data in series") else: - self.df.to_sql(name="DataValues", if_exists='replace', con=self.mem_service._session_factory.engine, + self.df.to_sql(name="odm2.timeseriesresultvalues", if_exists='replace', con=self.mem_service._session_factory.engine, index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 22e89c4..7eee9b6 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -1,22 +1,17 @@ -# from service_manager import ServiceManager -# from series_service import SeriesService -# from cv_service import CVService -# from edit_service import EditService -# from export_service import ExportService +from service_manager import ServiceManager +from series_service import SeriesService +from edit_service import EditService +from export_service import ExportService # # # need to explicitly import these for pyinstaller # import pymysql # import pyodbc # #import psycopg2 -from odm2api.ODM1_1_1.services import EditService, ExportService#, , CVService, SeriesService, -from series_service import SeriesService -from service_manager import ServiceManager __all__ = [ 'EditService', - #'CVService', 'SeriesService', 'ExportService', 'ServiceManager', diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 8c3bcb9..15dda6c 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -1,10 +1,10 @@ import sqlite3 -from odmtools.odmdata import DataValue +# from odmtools.odmdata import DataValue from series_service import SeriesService -from odmtools.odmdata import series as series_module +# from odmtools.odmdata import series as series_module import pandas as pd import datetime diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4891a51..2fe5d04 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -77,7 +77,7 @@ def get_all_series(self): """ return self.read.getDetailedResultInfo('Time Series Coverage') - def get_series_by_id(self, series_id): + def get_series(self, series_id=None): """ :param series_id: int :return: Series @@ -355,19 +355,6 @@ def get_series_by_site(self , site_id): # return None return self.read.getResults(type="timeSeries", ids=[site_id])[0] - - def get_series_by_id(self, series_id): - """ - - :param series_id: int - :return: Series - """ - # try: - # return self._edit_session.query(Series).filter_by(id=series_id).first() - # except Exception as e: - # print e - # return None - return self.read.getResults(type="timeSeries", ids=[series_id])[0] # # def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): # """ diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 8765b08..c88e465 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -6,7 +6,9 @@ from sqlalchemy.exc import SQLAlchemyError -from odmtools.odmservices import SeriesService, EditService, ExportService +from series_service import SeriesService +from edit_service import EditService +from export_service import ExportService from odmtools.controller import EditTools diff --git a/tests/test_odmdata/test_FreeTDS.py b/tests/test_odmdata/test_FreeTDS.py index 9f30a40..48bf488 100644 --- a/tests/test_odmdata/test_FreeTDS.py +++ b/tests/test_odmdata/test_FreeTDS.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +#TODO remove variable and series access here from odmtools.odmdata import SessionFactory, variable, series from odmtools.odmservices import SeriesService from tests import test_util From e95bf2f3196b4bb3176ad359a20c223f6d83e88f Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 3 Nov 2016 13:57:53 -0600 Subject: [PATCH 055/158] Some todos. Need to update the connection in the edit service --- odmtools/controller/frmAddPoints.py | 13 ++++++++----- odmtools/controller/logicEditTools.py | 4 ++-- odmtools/odmservices/edit_service.py | 7 ++++--- odmtools/odmservices/service_manager.py | 3 +-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index bb9eb63..652f633 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -8,6 +8,9 @@ except ImportError: import wx.lib.agw.genericmessagedialog as GMD import logging +from odmtools.odmservices.edit_service import EditService +from odmtools.controller.logicEditTools import EditTools + logger =logging.getLogger('main') # Implementing AddPoints @@ -227,11 +230,6 @@ def onSelected(self, event): event.Skip() def parseTable(self): - """ - - :return: - """ - series = self.recordService.get_series() objects = self.olv.GetObjects() @@ -301,6 +299,11 @@ def onCheck(self, event): class Example(wx.Frame): def __init__(self, parent, *args, **kwargs): wx.Frame.__init__(self, parent, *args, **kwargs) + edit_service = EditService(series_id=2, connection_string="pymysql+mysql://ODM:ODM123!!@jws.uwrl.usu.edu/odm2" ) + edit_tool = EditTools(script="", edit_service=edit_service, connection_string="abc123") + + kwargs["record_service"] = edit_tool + m = AddPoints(parent) m.Show() diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index 450bf09..7122515 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -11,13 +11,13 @@ class EditTools(): # Script header (imports etc.) will be set up in Main when record is clicked. - def __init__(self, parent, script, edit_service, connection_string, record=False): + def __init__(self, script, edit_service, connection_string, record=False): self._script = script # logger.debug(dir(self._script))sr self._edit_service = edit_service self._connection_string = connection_string self._record = record - self._serv_man = parent + # self._serv_man = parent self._edit_error = "no series_service selected for editing" self._add_point_req_error = "A required field was left empty" diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 15dda6c..c0370f7 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -48,12 +48,13 @@ def __init__(self, series_id, connection=None, connection_string="", debug=False self._filter_from_selection = False self._debug = debug - if connection_string is "" and connection is not None: - self.memDB= connection + if connection_string is "" and connection is not None: + self.memDB = connection elif connection_string is not "" and connection is None: from odmtools.odmdata import MemoryDatabase - self.memDB= MemoryDatabase() + self.memDB = MemoryDatabase() + # todo Stephanie: does not accept a string for the connection anymore self.memDB.set_series_service(SeriesService(connection_string, False)) diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index c88e465..19043b6 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -158,8 +158,7 @@ def get_edit_service(self, series_id, connection): # todo: Not using build_connection_string. Need to update this def get_record_service(self, script, series_id, connection): - return EditTools(self, script, self.get_edit_service(series_id, connection), - self._build_connection_string(self.is_valid_connection())) + return EditTools(script, self.get_edit_service(series_id, connection), connection_string=connection) def get_export_service(self): return ExportService(self.get_series_service()) From 51bac3049a4f8834e65d6a0d7ea72478d8487f54 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 3 Nov 2016 14:22:41 -0600 Subject: [PATCH 056/158] Fixed the time aggregation unit column I had requested the wrong data before. This should be correct now --- odmtools/controller/frmAddPoints.py | 8 ++++---- odmtools/controller/logicCellEdit.py | 4 ++-- odmtools/controller/olvAddPoint.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index 652f633..0c77590 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -299,10 +299,10 @@ def onCheck(self, event): class Example(wx.Frame): def __init__(self, parent, *args, **kwargs): wx.Frame.__init__(self, parent, *args, **kwargs) - edit_service = EditService(series_id=2, connection_string="pymysql+mysql://ODM:ODM123!!@jws.uwrl.usu.edu/odm2" ) - edit_tool = EditTools(script="", edit_service=edit_service, connection_string="abc123") - - kwargs["record_service"] = edit_tool + # edit_service = EditService(series_id=2, connection_string="pymysql+mysql://ODM:ODM123!!@jws.uwrl.usu.edu/odm2" ) + # edit_tool = EditTools(script="", edit_service=edit_service, connection_string="abc123") + # + # kwargs["record_service"] = edit_tool m = AddPoints(parent) m.Show() diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 7ff464d..71cbc6b 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -60,8 +60,8 @@ def fetchQualityCodeChoices(self): def fetchTimeUnitChoices(self): if not self.serviceManager: return [NULL] - a = self.series_service.read.getCVs("aggregationstatistic") - return [NULL] + [x.Term for x in self.series_service.get_aggregation_statistic()] + units = self.series_service.read.getUnits() + return [NULL] + [unit.UnitsName for unit in units] """ -------------------- diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 178eed8..38ce551 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -27,7 +27,7 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.valueDateTime = self.date self.utcOffSet = str(utcOffSet) - self.valueDateTimeUTFOffset = -1 + self.utfOffset = -1 self.offSetValue = offSetValue self.censorCode = censorCode self.qualityCodeCV = "NULL" @@ -142,7 +142,7 @@ def buildOlv(self): ColumnDefn(title="ValueDateTime", align="left", valueGetter="valueDateTime", minimumWidth=123, cellEditorCreator=self.valueDateTimeEditorCreator, headerImage="star"), - ColumnDefn(title="ValueDateTimeUTFOffset", align="left", valueGetter="valueDateTimeUTFOffset", + ColumnDefn(title="UTFOffset", align="left", valueGetter="utfOffset", minimumWidth=130, headerImage="star"), ColumnDefn(title="Quality CodeCV", align="left", valueGetter="qualityCodeCV", From c9675a0b6acd9e8c0746175eef8e44b25b258813 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 7 Nov 2016 09:55:09 -0700 Subject: [PATCH 057/158] update editing calls --- odmtools/common/taskServer.py | 4 +++- odmtools/controller/frmSeriesSelector.py | 1 + odmtools/odmdata/memory_database.py | 10 ++++++++-- odmtools/odmservices/series_service.py | 2 ++ odmtools/odmservices/service_manager.py | 2 +- tests/test_odmdata/test_FreeTDS.py | 4 +++- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index f1fde26..ec373f4 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -130,9 +130,11 @@ def worker(cls, dispatcher): result = Statistics(task) if task_type == "InitEditValues": connection = SeriesService("sqlite:///:memory:") + # connection._ df = task[1] logger.debug("Load series from db") - df.to_sql(name="odm2.timeseriesresultvalues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) + #setSchema(self.mem_service._session_factory.engine) + df.to_sql(name="timeseriesresultvalues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) logger.debug("done loading database") result = connection diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index dd06b93..6a259b4 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -30,6 +30,7 @@ def __init__(self, *args, **kwargs): self.memDB = kwargs.pop("memdb") self.pnlPlot = kwargs.pop("plot") + clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) def initPubSub(self): diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index acfed20..a5095be 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -9,6 +9,7 @@ # from odmtools.odmdata import SeriesService#ODM # ODM = SeriesService.ODM from odm2api.ODM2.models import TimeSeriesResultValues as DataValue +from odm2api.ODM2.models import setSchema logger =logging.getLogger('main') @@ -27,6 +28,7 @@ def __init__(self, taskserver=None): # Memory_service handles in memory database sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + setSchema(self.mem_service._session_factory.engine) # TODO clean up closing of program # if taskserver is None: @@ -35,10 +37,14 @@ def __init__(self, taskserver=None): #else: self.taskserver = taskserver + #send in engine + + def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + setSchema(self.mem_service._session_factory.engine) def set_series_service(self, service): @@ -229,8 +235,8 @@ def initEditValues(self, seriesID): if self.df is not None and len(self.df)<=0: logger.debug("no data in series") else: - - self.df.to_sql(name="odm2.timeseriesresultvalues", if_exists='replace', con=self.mem_service._session_factory.engine, + setSchema(self.mem_service._session_factory.engine) + self.df.to_sql(name="timeseriesresultvalues", if_exists='replace', con=self.mem_service._session_factory.engine, index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 2fe5d04..dcfe012 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -18,6 +18,8 @@ def __init__(self, connection, debug=False): self.update = UpdateODM2(self._session_factory) self.delete = DeleteODM2(self._session_factory) self.create = CreateODM2(self._session_factory) + #send in engine + setSchema(self._session_factory.engine) def reset_session(self): diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index c88e465..50f6a08 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -159,7 +159,7 @@ def get_edit_service(self, series_id, connection): # todo: Not using build_connection_string. Need to update this def get_record_service(self, script, series_id, connection): return EditTools(self, script, self.get_edit_service(series_id, connection), - self._build_connection_string(self.is_valid_connection())) + connection) def get_export_service(self): return ExportService(self.get_series_service()) diff --git a/tests/test_odmdata/test_FreeTDS.py b/tests/test_odmdata/test_FreeTDS.py index 48bf488..e2957cc 100644 --- a/tests/test_odmdata/test_FreeTDS.py +++ b/tests/test_odmdata/test_FreeTDS.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- #TODO remove variable and series access here -from odmtools.odmdata import SessionFactory, variable, series +from odmtools.odmdata import SessionFactory +from odm2api.ODM2.models import Variables as variable +from odm2api.ODM2.models import Results as series from odmtools.odmservices import SeriesService from tests import test_util import sys From 6e1df4f9962bba27b3e09f34e4d3e537a3bf98eb Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 7 Nov 2016 10:32:06 -0700 Subject: [PATCH 058/158] fix plotting --- odmtools/controller/logicPlotOptions.py | 2 +- odmtools/odmservices/service_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 1f634da..31d3130 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -211,7 +211,7 @@ def getAllSeries(self): def getSeriesById(self, seriesID): try: - series = self.memDB.series_service.get_series(series=seriesID) + series = self.memDB.series_service.get_series(seriesID) self.memDB.series_service.reset_session() return series diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index dd114a6..450f04c 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -150,8 +150,8 @@ def get_series_service(self, conn_dict=None, conn_string=""): # version = float(self._current_conn_dict['version']) # # sf = SessionFactory(conn_string, self.debug, version = version) - ss= SeriesService(conn) - return ss + self.series_service= SeriesService(conn) + return self.series_service def get_edit_service(self, series_id, connection): return EditService(series_id, connection=connection, debug=self.debug) From 9a8a711c57741023bdb2113d5cb2832ed4c1d5c6 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 7 Nov 2016 11:19:29 -0700 Subject: [PATCH 059/158] update plot_values --- odmtools/odmdata/memory_database.py | 2 +- odmtools/odmservices/series_service.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index a5095be..f67970a 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -94,7 +94,7 @@ def getDataValuesforGraph(self, seriesID, noDataValue, startDate=None, endDate=N return self.series_service.get_plot_values(seriesID, noDataValue, startDate, endDate) def getEditDataValuesforGraph(self): - return self.mem_service.get_all_plot_values() + return self.mem_service.get_plot_values() def commit(self): self.mem_service._session.commit() diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index dcfe012..8600485 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -487,7 +487,7 @@ def calcSeason(row): # data["Season"] = data.apply(self.calcSeason, axis=1) # return data.set_index(data['LocalDateTime']) # - def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): + def get_plot_values(self, seriesID=None, noDataValue=None, startDate = None, endDate = None ): """ :param seriesID: :param noDataValue: @@ -510,14 +510,27 @@ def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = Non # return data - Values = self.get_values(seriesID) + Values = [] + if seriesID: + Values = self.get_values(seriesID) + else: + Values = self.get_values() + data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] # data = data[data['datavalue'] != noDataValue] - data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( - data['valuedatetime'] <= endDate)] + if startDate: + data = data[data['valuedatetime'] >= startDate] + if endDate: + data = data[data['valuedatetime'] <= endDate] + if noDataValue: + data = data[(data['datavalue'] != noDataValue)] + + # data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( + # data['valuedatetime'] <= endDate)] #data.set_index(data['LocalDateTime'], inplace=True) + data["month"] = data['valuedatetime'].apply(lambda x: x.month) data["year"] = data['valuedatetime'].apply(lambda x: x.year) data["season"] = data.apply(self.calcSeason, axis=1) From e946766ff3f8cadc2dd94ed508a45b2c0059007d Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Mon, 7 Nov 2016 11:19:41 -0700 Subject: [PATCH 060/158] api updates --- odmtools/odmservices/series_service.py | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 2fe5d04..96faa6b 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -276,9 +276,10 @@ def get_qualifier_by_code(self, code): # """ # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() # return result - return self.read.getAnnotations() ##todo: CHECK ON THIS + return self.read.getAnnotations(codes=[code])[0] ##todo: CHECK ON THIS # -# def get_qualifiers_by_series_id(self, series_id): + def get_qualifiers_by_series_id(self, series_id): + return self.read.getAnnotations(ids=[series_id])[0] ##todo: check on this # """ # # :param series_id: @@ -326,16 +327,18 @@ def get_method_by_description(self, method_code): # logger.error("method not found") # return result # -# -# def get_samples_by_series_id(self, series_id): -# """ -# -# :param series_id: -# :return: -# """ -# subquery = self._edit_session.query(DataValue.sample_id).outerjoin( -# Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() -# return self._edit_session.query(Sample).join(subquery).distinct().all() + +#todo: Take another look at this + # def get_samples_by_series_id(self, series_id): + # # """ + # # + # # :param series_id: + # # :return: + # # # """ + # # subquery = self._edit_session.query(DataValue.sample_id).outerjoin( + # # Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() + # # return self._edit_session.query(Sample).join(subquery).distinct().all() + # # # Series Catalog methods From 53c07e8bb9b232575d67d0e765c8eb3fed847769 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 7 Nov 2016 13:54:35 -0700 Subject: [PATCH 061/158] update get all plot values --- odmtools/odmdata/memory_database.py | 2 +- odmtools/odmservices/series_service.py | 75 +++++++++----------------- 2 files changed, 25 insertions(+), 52 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index f67970a..a5095be 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -94,7 +94,7 @@ def getDataValuesforGraph(self, seriesID, noDataValue, startDate=None, endDate=N return self.series_service.get_plot_values(seriesID, noDataValue, startDate, endDate) def getEditDataValuesforGraph(self): - return self.mem_service.get_plot_values() + return self.mem_service.get_all_plot_values() def commit(self): self.mem_service._session.commit() diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 8600485..56cde50 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -466,28 +466,7 @@ def calcSeason(row): elif month in [10, 11, 12]: return 4 -# -# def get_all_plot_values(self): -# """ -# -# :return: -# """ -# q = self._edit_session.query(DataValue.data_value.label('DataValue'), -# DataValue.local_date_time.label('LocalDateTime'), -# DataValue.censor_code.label('CensorCode'), -# func.strftime('%m', DataValue.local_date_time).label('Month'), -# func.strftime('%Y', DataValue.local_date_time).label('Year') -# #DataValue.local_date_time.strftime('%m'), -# #DataValue.local_date_time.strftime('%Y')) -# ).order_by(DataValue.local_date_time) -# query = q.statement.compile(dialect=self._session_factory.engine.dialect) -# data = pd.read_sql_query(sql=query, -# con=self._session_factory.engine, -# params=query.params) -# data["Season"] = data.apply(self.calcSeason, axis=1) -# return data.set_index(data['LocalDateTime']) -# - def get_plot_values(self, seriesID=None, noDataValue=None, startDate = None, endDate = None ): + def get_plot_values(self, seriesID, noDataValue, startDate=None, endDate=None): """ :param seriesID: :param noDataValue: @@ -496,44 +475,38 @@ def get_plot_values(self, seriesID=None, noDataValue=None, startDate = None, end :return: """ - #series = self.get_series_by_id(seriesID) - # - # DataValues = [ - # (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), - # dv.local_date_time.strftime('%Y')) - # for dv in series.data_values - # if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate - # ] - # data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) - # data.set_index(data['LocalDateTime'], inplace=True) - # data["Season"] = data.apply(self.calcSeason, axis=1) - # return data - - - Values = [] - if seriesID: - Values = self.get_values(seriesID) - else: - Values = self.get_values() - + Values = self.get_values(seriesID) data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] # data = data[data['datavalue'] != noDataValue] - if startDate: - data = data[data['valuedatetime'] >= startDate] - if endDate: - data = data[data['valuedatetime'] <= endDate] - if noDataValue: - data = data[(data['datavalue'] != noDataValue)] + data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( + data['valuedatetime'] <= endDate)] - # data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( - # data['valuedatetime'] <= endDate)] + data["month"] = data['valuedatetime'].apply(lambda x: x.month) + data["year"] = data['valuedatetime'].apply(lambda x: x.year) + data["season"] = data.apply(self.calcSeason, axis=1) + # data.set_index(data['valuedatetime'], inplace=True) + return data + def get_all_plot_values(self): + """ + :param seriesID: + :param noDataValue: + :param startDate: + :param endDate: + :return: + """ + + + + Values = self.get_values() + data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] + # data = data[data['datavalue'] != noDataValue] - #data.set_index(data['LocalDateTime'], inplace=True) data["month"] = data['valuedatetime'].apply(lambda x: x.month) data["year"] = data['valuedatetime'].apply(lambda x: x.year) data["season"] = data.apply(self.calcSeason, axis=1) + #data.set_index(data['valuedatetime'], inplace=True) return data From d18bed04c190c49cce245e9f512c21b249707446 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 8 Nov 2016 13:20:39 -0700 Subject: [PATCH 062/158] editing working with schema work-around --- odmtools/controller/olvDataTable.py | 2 +- odmtools/gui/plotTimeSeries.py | 4 ++-- odmtools/odmdata/memory_database.py | 13 +++++++------ odmtools/odmservices/edit_service.py | 2 +- odmtools/odmservices/series_service.py | 7 ++++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 975f26c..e2b36b4 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -30,7 +30,7 @@ def init(self, memDB): self.oddRowsBackColor = wx.Colour(191, 217, 217) self.SetColumns(columns) self.dataframe = self.memDB.getDataValuesDF() - sort_by_index = list(self.dataframe.columns).index("LocalDateTime") + sort_by_index = list(self.dataframe.columns).index("valuedatetime") self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) self.dataObjects = self.dataframe.values.tolist() diff --git a/odmtools/gui/plotTimeSeries.py b/odmtools/gui/plotTimeSeries.py index 37db1ac..80cfb03 100644 --- a/odmtools/gui/plotTimeSeries.py +++ b/odmtools/gui/plotTimeSeries.py @@ -239,7 +239,7 @@ def drawEditPlot(self, oneSeries): dates = data.index.astype(datetime.datetime) curraxis = self.axislist[oneSeries.axisTitle] - curraxis.plot_date(dates, data['DataValue'], "-s", + curraxis.plot_date(dates, data['datavalue'], "-s", color=oneSeries.color, xdate=True, label=oneSeries.plotTitle, zorder=10, alpha=1, picker=5.0, pickradius=5.0, markersize=4.5) curraxis.set_xlabel('Date') @@ -250,7 +250,7 @@ def drawEditPlot(self, oneSeries): # scale = 1.5 # f = zoom_factory(curraxis , base_scale = scale) - self.xys = zip(convertedDates, oneSeries.dataTable['DataValue']) + self.xys = zip(convertedDates, oneSeries.dataTable['datavalue']) self.toolbar.editSeries(self.xys, self.editCurve) # self.pointPick = self.canvas.mpl_connect('pick_event', self._onPick) self.editSeries = oneSeries diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index a5095be..3278cff 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -80,12 +80,12 @@ def getEditRowCount(self): def getEditColumns(self): columns = [] tmp_columns = self.df.columns.tolist() - tmp_columns.remove('DataValue') - tmp_columns.remove('LocalDateTime') - tmp_columns.remove('QualifierID') - columns.append('DataValue') - columns.append('LocalDateTime') - columns.append('QualifierID') + tmp_columns.remove('datavalue') + tmp_columns.remove('valuedatetime') + #tmp_columns.remove('QualifierID') + columns.append('datavalue') + columns.append('valuedatetime') + #columns.append('QualifierID') columns.extend(tmp_columns) return [(x, i) for (i, x) in enumerate(columns)] # return [(x, i) for (i, x) in enumerate(self.df.columns)] @@ -219,6 +219,7 @@ def initEditValues(self, seriesID): if not self.editLoaded: logger.debug("Load series from db") + self.series = self.series_service.get_series(seriesID) self.df = self.series_service.get_values(series_id= seriesID) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index c0370f7..4c2cbac 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -309,7 +309,7 @@ def get_toggle(self): # Gets ################### def get_series(self): - return self.memDB.series_service.get_series_by_id(self._series_id) + return self.memDB.series_service.get_series(self._series_id) def get_series_points(self): # all point in the series_service diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4841f08..080d7c4 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -77,6 +77,7 @@ def get_all_series(self): Returns all series as a modelObject :return: List[Series] """ + setSchema(self._session_factory.engine) return self.read.getDetailedResultInfo('Time Series Coverage') def get_series(self, series_id=None): @@ -89,6 +90,7 @@ def get_series(self, series_id=None): # except Exception as e: # print e # return None + setSchema(self._session_factory.engine) return self.read.getResults(ids=[series_id])[0] # Query result objects for data purposes @@ -142,7 +144,7 @@ def get_values(self, series_id=None): # return data # else: # return None - + setSchema(self._session_factory.engine) q = self.read._session.query(TimeSeriesResultValues) if series_id: q = q.filter_by(ResultID=series_id) @@ -499,8 +501,7 @@ def get_all_plot_values(self): :return: """ - - + setSchema(self._session_factory.engine) Values = self.get_values() data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] # data = data[data['datavalue'] != noDataValue] From 13df5132d8cd15ee4718d1d2cb0fac1ae8f63f00 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 8 Nov 2016 14:54:17 -0700 Subject: [PATCH 063/158] add delete values --- odmtools/gui/plotTimeSeries.py | 4 +-- odmtools/odmservices/series_service.py | 49 +++++++++++++++----------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/odmtools/gui/plotTimeSeries.py b/odmtools/gui/plotTimeSeries.py index 80cfb03..221005e 100644 --- a/odmtools/gui/plotTimeSeries.py +++ b/odmtools/gui/plotTimeSeries.py @@ -123,7 +123,7 @@ def changePlotSelection(self, filtered_datetime): if isinstance(filtered_datetime, list): df = self.editCurve.dataTable - result = df[df['LocalDateTime'].isin(filtered_datetime)].astype(datetime.datetime) + result = df[df['valuedatetime'].isin(filtered_datetime)].astype(datetime.datetime) if isinstance(result, pd.DataFrame): if result.empty: @@ -134,7 +134,7 @@ def changePlotSelection(self, filtered_datetime): self.canvas.draw() return - values = result['DataValue'].values.tolist() + values = result['datavalue'].values.tolist() dates = result.index.astype(datetime.datetime) self.selplot = self.axislist[self.editSeries.axisTitle].scatter( dates, values, s=35, c='red', edgecolors='none', zorder=12, marker='s', alpha=1) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 080d7c4..ef5b912 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -769,12 +769,20 @@ def get_all_plot_values(self): # raise e # # -# def delete_values_by_series(self, series, startdate = None): -# """ -# -# :param series: -# :return: -# """ + def delete_values_by_series(self, series, startdate = None): + """ + + :param series: + :return: + """ + #todo stephanie: add startdate stuff + try: + self.delete.deleteTSRValues(ids = [series.id]) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex # try: # q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, # variable_id = series.variable_id, @@ -793,20 +801,21 @@ def get_all_plot_values(self): # logger.error(message) # raise ex # -# def delete_dvs(self, id_list): -# """ -# -# :param id_list: list of datetimes -# :return: -# """ -# try: -# self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) -# except Exception as ex: -# message = "Values were not successfully deleted: %s" % ex -# print message -# logger.error(message) -# raise ex -# + def delete_dvs(self, id_list): + """ + + :param id_list: list of datetimes + :return: + """ + try: + self.delete.deleteTSRValues(dates = id_list) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex + + # ##################### # # # #Exist functions From f9524c35b1cfd43b59d881232a4612b6fb7a2bc9 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 8 Nov 2016 15:03:34 -0700 Subject: [PATCH 064/158] more api --- odmtools/odmservices/series_service.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4841f08..12d9e88 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -609,13 +609,13 @@ def get_all_plot_values(self): # logger.info("A new series was added to the database, series id: "+str(series.id)) # return True # -# def save_values(self, values): -# """ -# -# :param values: pandas dataframe -# :return: -# """ -# values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) + def save_values(self, values): + """ + + :param values: pandas dataframe + :return: + """ + #values.to_sql(name="timeseriesresultvalues", if_exists='append', con=self._session_factory.engine, index=False) # # def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): # """ From 0e35efdda73b04a5ee8512aebce673933f84ec9f Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 8 Nov 2016 15:26:04 -0700 Subject: [PATCH 065/158] api --- odmtools/odmservices/series_service.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 69cd2e8..9878bab 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -616,7 +616,7 @@ def save_values(self, values): :param values: pandas dataframe :return: """ - #values.to_sql(name="timeseriesresultvalues", if_exists='append', con=self._session_factory.engine, index=False) + values.to_sql(name="timeseriesresultvalues", if_exists='append', con=self._session_factory.engine, index=False) # # def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): # """ @@ -641,7 +641,8 @@ def save_values(self, values): # self._edit_session.commit() # return series # -# def create_method(self, description, link): + def create_method(self, description, link): + self.create.createMethod(description, link) #todo: update api to reflect this # """ # # :param description: @@ -656,7 +657,7 @@ def save_values(self, values): # self._edit_session.add(meth) # self._edit_session.commit() # return meth -# +# # # def create_variable_by_var(self, var): # """ # From b4739eba2e7b3031be9b80fec04f31f26694951b Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 8 Nov 2016 17:28:29 -0700 Subject: [PATCH 066/158] update editing functions --- odmtools/gui/mnuPlotToolbar.py | 3 +- odmtools/odmdata/memory_database.py | 59 +++++++++++++++------------- odmtools/odmservices/edit_service.py | 27 ++++--------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/odmtools/gui/mnuPlotToolbar.py b/odmtools/gui/mnuPlotToolbar.py index 167900d..34e5e66 100644 --- a/odmtools/gui/mnuPlotToolbar.py +++ b/odmtools/gui/mnuPlotToolbar.py @@ -402,7 +402,8 @@ def SetString(self, tip): self.tooltip_string = tip def Enable(self, x): - print ("in custom tooltip set enable") + #todo fix tooltip for mac + #print ("in custom tooltip set enable") if x: self.SetTip(self.tooltip_string) else: self.SetTip("") diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 3278cff..8b4173b 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -8,7 +8,7 @@ # from odmtools.odmdata import SeriesService#ODM # ODM = SeriesService.ODM -from odm2api.ODM2.models import TimeSeriesResultValues as DataValue +from odm2api.ODM2.models import TimeSeriesResultValues as TSRV from odm2api.ODM2.models import setSchema @@ -114,12 +114,13 @@ def update(self, updates): updates : list of dictionary that contains 2 items, id and value ''' - stmt = (DataValue.__table__.update(). - where(DataValue.ValueDateTime == bindparam('id')). + stmt = (TSRV.__table__.update(). + where(TSRV.ValueDateTime == bindparam('id')). values(DataValue=bindparam('value')) - ) + ) self.mem_service._session.execute(stmt, updates) + #self.mem_service._session.query(TSRV).filter_by # self.updateDF() @@ -127,20 +128,21 @@ def update(self, updates): def updateValue(self, ids, operator, value): # query = DataValue.data_value+value if operator == '+': - query = DataValue.DataValue + value + query = TSRV.DataValue + value elif operator == '-': - query = DataValue.DataValue - value + query = TSRV.DataValue - value elif operator == '*': - query = DataValue.DataValue * value + query = TSRV.DataValue * value elif operator == '=': query = value #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) + setSchema(self.mem_service._session_factory.engine) for c in chunks: - q=self.mem_service._session.query(DataValue).filter(DataValue.ValueDateTime.in_(c)) - q.update({DataValue.DataValue: query}, False) + q=self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c)) + q.update({TSRV.DataValue: query}, False) #self.updateDF() @@ -156,8 +158,8 @@ def chunking(self, data): def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: - self.mem_service._session.query(DataValue).filter(DataValue.ValueDateTime.in_(c))\ - .update({DataValue.qualifier_id: value}, False) + self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ + .update({TSRV.qualifier_id: value}, False) def delete(self, ids): @@ -171,7 +173,7 @@ def addPoints(self, points): """ Takes in a list of points and loads each point into the database """ - stmt = DataValue.__table__.insert() + stmt = TSRV.__table__.insert() if not isinstance(points, list): points = [points] @@ -243,8 +245,8 @@ def initEditValues(self, seriesID): -#TODO: update to work with ODM2 - def changeSeriesIDs(self, var=None, qcl=None, method=None): + + def changeSeriesIDs(self, result): """ :param var: @@ -253,17 +255,20 @@ def changeSeriesIDs(self, var=None, qcl=None, method=None): :return: """ - query = self.mem_service._session.query(DataValue) - if var is not None: - logger.debug(var) - query.update({DataValue.variable_id: var}) - - if method is not None: - logger.debug(method) - query.update({DataValue.method_id: method}) - # check that the code is not zero - # if qcl is not None and qcl.code != 0: - if qcl is not None: - logger.debug(qcl) - query.update({DataValue.quality_control_level_id: qcl}) + query = self.mem_service._session.query(TSRV) + # if var is not None: + # logger.debug(var) + # query.update({DataValue.variable_id: var}) + # + # if method is not None: + # logger.debug(method) + # query.update({DataValue.method_id: method}) + # # check that the code is not zero + # # if qcl is not None and qcl.code != 0: + # if qcl is not None: + # logger.debug(qcl) + # query.update({DataValue.quality_control_level_id: qcl}) + logger.debug(result) + query.update({TSRV.ResultID:result}) + diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 4c2cbac..cb29444 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -483,7 +483,7 @@ def restore(self): self._populate_series() self.reset_filter() - def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, overwrite = True, append = False): + def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): """ :param var: @@ -493,18 +493,12 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove :return: """ - var_id = var.id if var is not None else None - method_id = method.id if method is not None else None - qcl_id = qcl.id if qcl is not None else None + result_id = result.ResultID if result is not None else None + #self.memDB.changeSeriesIDs(var_id, method_id, qcl_id) dvs = self.memDB.getDataValuesDF() - if var_id is not None: - dvs["VariableID"] = var_id - if method_id is not None: - dvs["MethodID"] = method_id - if qcl_id is not None: - dvs["QualityControlLevelID"] = qcl_id - + if result_id is not None: + dvs["ResultID"] = result_id #if is new series_service remove valueids @@ -518,14 +512,9 @@ def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, ove series = self.memDB.series_service.get_series_by_id(self._series_id) logger.debug("original editing series_service id: %s" % str(series.id)) - if (var or method or qcl ): - tseries = self.memDB.series_service.get_series_by_id_quint(site_id=int(series.site_id), - var_id=var_id if var else int(series.variable_id), - method_id=method_id if method else int( - series.method_id), - source_id=series.source_id, - qcl_id=qcl_id if qcl else int( - series.quality_control_level_id)) + if (result): + tseries = self.memDB.series_service.get_series(result_id) + if tseries: logger.debug("Save existing series_service ID: %s" % str(tseries.id)) series = tseries From 395edfce1e70d2b697da6d43ac6cad7c9d3925fb Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 9 Nov 2016 12:51:30 -0700 Subject: [PATCH 067/158] Fixing multiple bugs that was caused during the merge --- odmtools/controller/WizardMethodController.py | 6 +- .../WizardProcessLevelController.py | 8 +- .../controller/WizardVariableController.py | 33 ++--- odmtools/controller/pageExisting.py | 5 +- odmtools/gui/plotProbability.py | 16 +-- odmtools/gui/wizSave.py | 5 +- odmtools/odmservices/series_service.py | 117 +++++++----------- 7 files changed, 74 insertions(+), 116 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 5a31d95..dc51961 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -53,9 +53,9 @@ def __fetch_data(self): data = [] for meth in methods: data.append([ - meth.description, - meth.link, - meth.id + meth.MethodDescription, + meth.MethodLink, + meth.MethodID ]) self.method_view.existing_method_table.set_table_content(data=data) diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index f36d1b7..8ac705c 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -45,10 +45,10 @@ def __fetch_data(self): data = [] for proc in processes: data.append([ - proc.code, - proc.definition, - proc.explanation, - proc.id + proc.ProcessingLevelCode, + proc.Definition, + proc.Explanation, + proc.ProcessingLevelID ]) self.processing_level_view.existing_process_table.set_table_content(data=data) diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index c6417e8..e0d2fb1 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -1,9 +1,9 @@ import wx from odmtools.view.WizardVariableView import WizardVariableView from wx.wizard import WizardPageSimple -# from odmtools.odmdata import Variable from odm2api.ODM2.models import Variables as Variable + class WizardVariableController(WizardPageSimple): def __init__(self, parent, service_manager, current_variable): WizardPageSimple.__init__(self, parent) @@ -15,9 +15,7 @@ def __init__(self, parent, service_manager, current_variable): main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.RIGHT, -16) self.SetSizer(main_sizer) - table_columns = ["Code", "Name", "Speciation", "Units", - "Sample Medium", "Value Type", "IsRegular", "Time Support", - "Time Units", "DataType", "Genaral Category", "NoDataValue", "ID"] + table_columns = ["Code", "Name", "Speciation", "DataType", "NoDataValue", "ID"] self.variable_view.variable_table.set_columns(table_columns) self.on_current_radio(None) @@ -52,10 +50,10 @@ def __set_create_variable_section(self, active): def __fetch_data(self): self.__populate_variable_table() - cv_service = self.service_manager.get_cv_service() - name_list = [x.term for x in cv_service.get_variable_name_cvs()] - var_unit = [x.name for x in cv_service.get_units_names()] - spec_list = [x.term for x in cv_service.get_speciation_cvs()] + series_service = self.service_manager.get_series_service() + name_list = [x.Term for x in series_service.get_variable_name_cvs()] + var_unit = [x.UnitsName for x in series_service.get_units()] + spec_list = [x.Term for x in series_service.get_speciation_cvs()] self.variable_view.variable_name_combo.AppendItems(name_list) self.variable_view.speciation_combo.AppendItems(spec_list) @@ -66,19 +64,12 @@ def __populate_variable_table(self): variables = series_serivce.get_all_variables() data = [] for var in variables: - data.append([var.code, - var.name, - var.speciation, - var.variable_unit.name, - var.sample_medium, - var.value_type, - var.is_regular, - var.time_support, - var.time_unit.name, - var.data_type, - var.general_category, - var.no_data_value, - var.id]) + data.append([var.VariableCode, + var.VariableNameCV, + var.SpeciationCV, + var.VariableTypeCV, + var.NoDataValue, + var.VariableID]) self.variable_view.variable_table.set_table_content(data=data) diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index ee58bcc..7d5e780 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -16,7 +16,6 @@ class pageExisting(wiz.WizardPageSimple): def __init__(self, parent, title, series_service , site): """Constructor""" wiz.WizardPageSimple.__init__(self, parent) - sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = sizer self.SetSizer(sizer) @@ -30,7 +29,7 @@ def __init__(self, parent, title, series_service , site): #pos=wx.Point(536, 285), size=wx.Size(439, 357), #style=wx.TAB_TRAVERSAL)#, sm = service_man, series_service = series_service) self.sizer.Add(self.pnlExisting, 85, wx.ALL, 5) - self._init_data(series_service, site.id) + self._init_data(series_service, site.SamplingFeatureID) self.pnlExisting.olvSeriesList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnOLVItemSelected) @@ -80,5 +79,5 @@ def initTable(self, dbservice, site_id): for key, value in returnDict().iteritems()] self.pnlExisting.olvSeriesList.SetColumns(seriesColumns) - objects = dbservice.get_series_by_site(site_id= site_id) + objects = dbservice.get_series_by_site(site_id=site_id) self.pnlExisting.olvSeriesList.SetObjects(objects) diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 19f5b03..9dae51b 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -133,20 +133,10 @@ def updatePlot(self): self.plots.set_title("\n".join(textwrap.wrap(oneSeries.siteName, 55))) if len(oneSeries.dataTable) > 0: + xValues = oneSeries.Probability.xAxis.sort_values().values + yValues = oneSeries.Probability.yAxis.sort_values().values - #self.prob.append( - #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) - #todo FutureWarning: order is deprecated, use sort_values(...) - - # xValues = oneSeries.Probability.xAxis.sort_values().values - # yValues = oneSeries.Probability.yAxis.sort_values().values - - xValues = oneSeries.Probability.xAxis.order().values - yValues = oneSeries.Probability.yAxis.order().values - - - ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, - label=oneSeries.plotTitle) + ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) self.axislist[oneSeries.axisTitle] = ax[0] self.setXaxis() diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index d88d3af..ee2796b 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -273,8 +273,9 @@ def __init__(self, parent, service_manager, record_service): self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager) # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) self.pgVariable = WizardVariableController(self, service_manager=service_manager, - current_variable=self.currSeries.variable) - self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) + current_variable=self.currSeries.VariableObj) + self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, + self.currSeries.FeatureActionObj.SamplingFeatureObj) self.pgSummary = SummaryPage(self, "Summary", self.series_service) self.FitToPage(self.pgIntro) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 9878bab..9b9bc05 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -121,55 +121,56 @@ def get_variables_by_site_code(self, site_code): return q.all() # Data Value Methods - def get_values(self, series_id=None): - ''' - - :param series_id: Series id - :return: pandas dataframe - ''' - # series= self.get_series_by_id(series_id) - # if series: - # q = self._edit_session.query(DataValue).filter_by( - # site_id=series.site_id, - # variable_id=series.variable_id, - # method_id=series.method_id, - # source_id=series.source_id, - # quality_control_level_id=series.quality_control_level_id) - # - # query=q.statement.compile(dialect=self._session_factory.engine.dialect) - # data= pd.read_sql_query(sql= query, - # con = self._session_factory.engine, - # params = query.params ) - # #return data.set_index(data['LocalDateTime']) - # return data - # else: - # return None - setSchema(self._session_factory.engine) - q = self.read._session.query(TimeSeriesResultValues) - if series_id: - q = q.filter_by(ResultID=series_id) - q = q.order_by(TimeSeriesResultValues.ValueDateTime) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data.set_index(data['valuedatetime'], inplace=True) - return data + def get_values(self, series_id=None): + ''' + + :param series_id: Series id + :return: pandas dataframe + ''' + # series= self.get_series_by_id(series_id) + # if series: + # q = self._edit_session.query(DataValue).filter_by( + # site_id=series.site_id, + # variable_id=series.variable_id, + # method_id=series.method_id, + # source_id=series.source_id, + # quality_control_level_id=series.quality_control_level_id) + # + # query=q.statement.compile(dialect=self._session_factory.engine.dialect) + # data= pd.read_sql_query(sql= query, + # con = self._session_factory.engine, + # params = query.params ) + # #return data.set_index(data['LocalDateTime']) + # return data + # else: + # return None + setSchema(self._session_factory.engine) + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, + con=self._session_factory.engine, + params=query.params) + data.set_index(data['valuedatetime'], inplace=True) + return data # Series Catalog methods def get_series_by_site(self , site_id): - """ - :param site_id: int - :return: List[Series] - """ # try: # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() # return selectedSeries # except: # return None + """ + :param site_id: type(Int) + :return: list[Series] + """ - return self.read.getResult(type="site", ids = [site_id])[0] - ##TODO : check is this the right way to get the series?? + # return self.read.getResults(type="timeSeries", ids=[site_id]) + # return self.read.getResults(type="site", ids= [site_id])[0] + return self.read.getResults(ids=[site_id]) @@ -284,6 +285,10 @@ def get_qualifier_by_code(self, code): # def get_qualifiers_by_series_id(self, series_id): return self.read.getAnnotations(ids=[series_id])[0] ##todo: check on this + + def get_all_processing_levels(self): + return self.read.getProcessingLevels(ids=None, codes=None) + # """ # # :param series_id: @@ -345,24 +350,6 @@ def get_method_by_description(self, method_code): # # # Series Catalog methods - -# - - #TODO siteid should actually be joined through featureaction and filtered that way - def get_series_by_site(self , site_id): - """ - - :param site_id: int - :return: List[Series] - # """ - # try: - # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() - # return selectedSeries - # except: - # return None - return self.read.getResults(type="timeSeries", ids=[site_id])[0] - -# # def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): # """ # @@ -493,14 +480,6 @@ def get_plot_values(self, seriesID, noDataValue, startDate=None, endDate=None): return data def get_all_plot_values(self): - """ - :param seriesID: - :param noDataValue: - :param startDate: - :param endDate: - :return: - """ - setSchema(self._session_factory.engine) Values = self.get_values() data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] @@ -1058,12 +1037,10 @@ def get_units(self): return self.read.getUnits(ids=None, name=None, type=None) def get_units_not_uni(self): - result = self._session.query(Units).filter(not_(Units.name.contains('angstrom'))).all() - return result + return self._session.query(Units).filter(not_(Units.UnitsName.contains('angstrom'))).all() def get_units_names(self): - result = self._session.query(Units.name).all() - return result + return self._session.query(Units.UnitsName).all() def get_quality_code(self): return self.read.getCVs(type="Quality Code") From c7e4593fb6d748ad1e38ffc72a91f1625e312be3 Mon Sep 17 00:00:00 2001 From: stephanie Date: Wed, 9 Nov 2016 14:00:01 -0700 Subject: [PATCH 068/158] update flag value --- odmtools/gui/mnuRibbon.py | 6 +++--- odmtools/odmdata/memory_database.py | 3 +++ odmtools/odmservices/series_service.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index 1ff959b..8e85981 100644 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -395,7 +395,7 @@ def onEditFilter(self, event): data_filter = frmDataFilter(self.parent, self.parent.getRecordService()) if data_filter.Show() == wx.OK: - print "OK" + # print "OK" data_filter.Destroy() event.Skip() @@ -439,8 +439,8 @@ def onEditFlag(self, event): serviceManager = self.parent.getDBService() series_service = serviceManager.get_series_service() - qualifierChoices = OrderedDict((x.code + '-' + x.description, x.id) for x in series_service.get_all_qualifiers() - if x.code and x.description) + qualifierChoices = OrderedDict((x.AnnotationCode + '-' + x.AnnotationText, x.AnnotationID) for x in series_service.get_all_qualifiers() + if x.AnnotationCode and x.AnnotationText) add_flag = frmFlagValues(self.parent, series_service, qualifierChoices) val = add_flag.ShowModal() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 8b4173b..1a7c018 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -158,9 +158,12 @@ def chunking(self, data): def updateFlag(self, ids, value): chunks=self.chunking(ids) for c in chunks: + # add entry in the Timeseriesresultvalueannotations table self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ .update({TSRV.qualifier_id: value}, False) + + def delete(self, ids): chunks=self.chunking(ids) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 9878bab..9a8048b 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -271,7 +271,8 @@ def get_all_qualifiers(self): """ # result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() # return result - return self.read.getAnnotations() + ann= self.read.getAnnotations() + return ann # def get_qualifier_by_code(self, code): """ From 7c0aa70f320623197df18d4b9b56aeb3012fcdc7 Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 9 Nov 2016 14:46:20 -0700 Subject: [PATCH 069/158] mergin --- odmtools/gui/plotProbability.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 9dae51b..bcc3ddc 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -136,7 +136,9 @@ def updatePlot(self): xValues = oneSeries.Probability.xAxis.sort_values().values yValues = oneSeries.Probability.yAxis.sort_values().values - ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) + ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, + label=oneSeries.plotTitle) + self.axislist[oneSeries.axisTitle] = ax[0] self.setXaxis() From ea066f858d4fd8dbbf42260b7786f19907d22faf Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 9 Nov 2016 17:15:16 -0700 Subject: [PATCH 070/158] update linear drift, add error messaging --- odmtools/controller/frmLinearDrift.py | 6 +++++- odmtools/gui/mnuRibbon.py | 7 ++++++- odmtools/odmdata/memory_database.py | 1 - odmtools/odmservices/edit_service.py | 8 ++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/odmtools/controller/frmLinearDrift.py b/odmtools/controller/frmLinearDrift.py index 86cb131..e991f8d 100644 --- a/odmtools/controller/frmLinearDrift.py +++ b/odmtools/controller/frmLinearDrift.py @@ -17,10 +17,14 @@ def OnBtnOKButton(self, event): if not result: dial = wx.MessageDialog( None, "Linear drift can only be performed on one continuous data selection. \nPlease modify your selection and try again.", "Bad Input", wx.OK) dial.ShowModal() - except Exception as e: + except ValueError as e: dial = wx.MessageDialog(None, "Unable to convert value to float %s" % e, "Bad Input", wx.OK | wx.ICON_ERROR) dial.ShowModal() + except Exception as e: + dial = wx.MessageDialog(None, "Unable perform linear drift %s" % e, "Bad Input", + wx.OK | wx.ICON_ERROR) + dial.ShowModal() self.Close() def OnBtnCancelButton(self, event): diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index 8e85981..f5b11a0 100644 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -428,7 +428,12 @@ def onEditInterpolate(self, event): "You have chosen to interpolate the %s selected points.\nDo you want to continue?" % len(dataframe), 'Interpolation', wx.YES_NO | wx.ICON_QUESTION | wx.CENTRE, parent=self.parent) if val == 2: # wx.ID_YES: - self.parent.getRecordService().interpolate() + try: + self.parent.getRecordService().interpolate() + except Exception as e: + dial = wx.MessageDialog(None, "Unable perform interplation %s" % e, "Bad Input", + wx.OK | wx.ICON_ERROR) + dial.ShowModal() event.Skip() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 1a7c018..859babd 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -213,7 +213,6 @@ def updateDF(self): else: ''' self.df = self.mem_service.get_values() - print self.mem_service._version def initEditValues(self, seriesID): diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index cb29444..55f745b 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -417,12 +417,12 @@ def drift_correction(self, gap_width): tmp_filter_list =self.get_filtered_points() startdate =tmp_filter_list.index[0] x_l = (tmp_filter_list.index[-1]-startdate).total_seconds() - #nodv= self.memDB.series_service.get_variable_by_id(self.memDB.df["VariableID"][0]) - nodv = self.memDB.series.variable.no_data_value - # y_n = y_0 + G(x_i / x_l) + nodv = self.memDB.series.VariableObj.NoDataValue + + # y_n = y_0 + G(x_i / x_l) # f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) - # tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) + f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) if row["datavalue"] != nodv else row["datavalue"] tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) From 0dad751ebd1dda3312f6e81a21bfdc7fb5ea83ad Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 9 Nov 2016 17:29:13 -0700 Subject: [PATCH 071/158] fix the update function --- odmtools/odmdata/memory_database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 859babd..f5354ee 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -113,10 +113,10 @@ def update(self, updates): ''' updates : list of dictionary that contains 2 items, id and value ''' - + setSchema(self.mem_service._session_factory.engine) stmt = (TSRV.__table__.update(). where(TSRV.ValueDateTime == bindparam('id')). - values(DataValue=bindparam('value')) + values(datavalue=bindparam('value')) ) self.mem_service._session.execute(stmt, updates) From 25c71e809f5dda881c23afc8314ce0be8739583e Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 10 Nov 2016 14:22:47 -0700 Subject: [PATCH 072/158] Fixed the finished on addpoint form. Clicking on finished should add the data to the database. Need to verify this --- odmtools/controller/frmAddPoints.py | 64 +++++++++++------------------ odmtools/controller/olvAddPoint.py | 23 +---------- odmtools/odmdata/memory_database.py | 18 ++++---- 3 files changed, 35 insertions(+), 70 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index 0c77590..b3ce315 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -152,19 +152,9 @@ def onInfoBtn(self, event): event.Skip() def onFinishedBtn(self, event): - """ - - :param event: - :return: - """ self.checkIfEditing() - #try: points, isIncorrect = self.parseTable() - #except: - # return - - message = "" if not points and not isIncorrect: #print "Leaving..." @@ -232,42 +222,34 @@ def onSelected(self, event): def parseTable(self): series = self.recordService.get_series() + site_id = series.FeatureActionObj.SamplingFeatureID + variable_id = series.VariableID + method_id = series.FeatureActionObj.ActionObj.MethodID + organization_id = series.FeatureActionObj.ActionObj.MethodObj.OrganizationID + process_id = series.ProcessingLevelID + objects = self.olv.GetObjects() isIncorrect = False points = [] - for i in objects: - if self.olv.isCorrect(i): - row = [None] * 10 - if i.valueAccuracy != "NULL": - row[1] = i.valueAccuracy - if i.offSetType != "NULL": - row[6] = i.offSetType - if i.qualifierCode != "NULL": - code = i.qualifierCode.split(':')[0] - q=self.recordService._edit_service.memDB.series_service.get_qualifier_by_code(code=code) - row[8] = q.id - if i.labSampleCode != "NULL": - row[9] = i.labSampleCode - - row[0] = i.dataValue - - dt = self.combineDateTime(i.date, i.time) - row[2] = dt - ## UTC Offset - row[3] = i.utcOffSet - ## Calculate UTC time based off the localdatetime and utcOffSet - row[4] = dt - datetime.timedelta(hours=int(i.utcOffSet)) - row[7] = i.censorCode - - row.extend([ - series.site_id, series.variable_id, series.method_id, - series.source_id, series.quality_control_level_id - ] - ) - - points.append(tuple(row)) + for point in objects: + if self.olv.isCorrect(point): + row = [None] * self.olv.GetColumnCount() + row[0] = point.dataValue + dt = self.combineDateTime(point.date, point.time) + row[1] = dt + row[2] = dt - datetime.timedelta(hours=int(point.utcOffSet)) # Calculates the time offset + row[3] = point.utcOffSet + row[4] = point.censorCode + row[5] = point.qualityCodeCV + row[6] = point.timeAggInterval + row[7] = point.timeAggregationUnitID + row[8] = point.annotation + + row.extend([site_id, variable_id, method_id, organization_id, process_id]) + + points.append(row) else: isIncorrect = True diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 38ce551..7eb953f 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -10,14 +10,11 @@ class Points(object): def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, - censorCode="NULL", valueAccuracy="NULL", offSetValue="NULL"): + censorCode="NULL", offSetValue="NULL"): try: self.dataValue = str(dataValue) except: self.dataValue = dataValue - - self.valueAccuracy = valueAccuracy - try: self.time = str(time) except: @@ -27,7 +24,6 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.valueDateTime = self.date self.utcOffSet = str(utcOffSet) - self.utfOffset = -1 self.offSetValue = offSetValue self.censorCode = censorCode self.qualityCodeCV = "NULL" @@ -44,8 +40,6 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.validValueAcc = False self.validOffSetValue = False self.validOffSetType = False - self.validQualifierCode = False - self.validLabSampleCode = False class OLVAddPoint(FastObjectListView): @@ -75,9 +69,6 @@ def __init__(self, *args, **kwargs): self.imgGetterTime = cellEdit.imgGetterTime self.imgGetterCensorCode = cellEdit.imgGetterCensorCode self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset - self.imgGetterValueAcc = cellEdit.imgGetterValueAcc - self.imgGetterQualifier = cellEdit.imgGetterQualifierCode - self.imgGetterOffSetType = cellEdit.imgGetterOffSetType self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue ## Custom Value Setters @@ -138,13 +129,6 @@ def buildOlv(self): ColumnDefn("CensorCode", "left", -1, valueGetter="censorCode", minimumWidth=110, cellEditorCreator=self.censorEditor, imageGetter=self.imgGetterCensorCode, headerImage="star"), - # valueGetter needs to be created in the Points class - ColumnDefn(title="ValueDateTime", align="left", valueGetter="valueDateTime", - minimumWidth=123, cellEditorCreator=self.valueDateTimeEditorCreator, headerImage="star"), - - ColumnDefn(title="UTFOffset", align="left", valueGetter="utfOffset", - minimumWidth=130, headerImage="star"), - ColumnDefn(title="Quality CodeCV", align="left", valueGetter="qualityCodeCV", minimumWidth=130, cellEditorCreator=self.qualityCodeCreator, imageGetter="star"), @@ -156,7 +140,6 @@ def buildOlv(self): ColumnDefn(title="Annotation", align="left", minimumWidth=130, valueGetter="annotation", cellEditorCreator=self.annotationCreator, headerImage="star") - ] self.SetColumns(columns) @@ -173,13 +156,11 @@ def rowFormatter(listItem, point): listItem.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)) self.rowFormatter = rowFormatter - self.AutoSizeColumns() def isCorrect(self, point): validators = [ self.imgGetterDataValue, self.imgGetterDate, self.imgGetterTime, self.imgGetterCensorCode, - self.imgGetterUTCOffset, self.imgGetterValueAcc, self.imgGetterlabSample, - self.imgGetterQualifier, self.imgGetterOffSetType, self.imgGetterOffSetValue + self.imgGetterUTCOffset, self.imgGetterOffSetValue ] isCorrect = True diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index f5354ee..5403550 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -182,14 +182,16 @@ def addPoints(self, points): points = [points] for point in points: - vals = {"DataValue": point[0], "ValueAccuracy": point[1], - "LocalDateTime": point[2], "UTCOffset": point[3], - "DateTimeUTC": point[4], "OffsetValue": point[5], - "OffsetTypeID": point[6], "CensorCode": point[7], - "QualifierID": point[8], "SampleID": point[9], - "SiteID": point[10], "VariableID": point[11], - "MethodID": point[12], "SourceID": point[13], - "QualityControlLevelID": point[14]} + vals = {"DataValue": point[0], "LocalDateTime": point[1], + "DateTimeUTC": point[2], "UTCOffset": point[3], + "CensorCode": point[4], "QualityCode": point[5], + "TimeAggregationInterval": point[6], "TImeAggregationUnitID": point[7], + "Annotation": point[8], "SiteID": point[9], + "VariableID": point[10], "MethodID": point[11], + "OrganizationID": point[12], "ProcessID": point[13] + } + + setSchema(self.mem_service._session_factory.engine) self.mem_service._session.execute(stmt, vals) From f2596b642f223e4ebf731685426d0f59c28c3c3f Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Thu, 10 Nov 2016 15:28:46 -0700 Subject: [PATCH 073/158] some create api updates --- odmtools/odmservices/series_service.py | 66 ++++++++++++++------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 2987e56..975c58d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -638,24 +638,28 @@ def create_method(self, description, link): # self._edit_session.commit() # return meth # # -# def create_variable_by_var(self, var): -# """ -# -# :param var: Variable Object -# :return: -# """ -# try: -# self._edit_session.add(var) -# self._edit_session.commit() -# return var -# except: -# return None + def create_variable_by_var(self, var): + try: + return self.create.createVariable(var) + except: + return None + # """ + # + # :param var: Variable Object + # :return: + # """ + # try: + # self._edit_session.add(var) + # self._edit_session.commit() + # return var + # except: + # return None # -# def create_variable( -# self, code, name, speciation, variable_unit_id, sample_medium, -# value_type, is_regular, time_support, time_unit_id, data_type, -# general_category, no_data_value): -# """ + def create_variable( + self, code, name, speciation, variable_unit_id, sample_medium, + value_type, is_regular, time_support, time_unit_id, data_type, + general_category, no_data_value): + """ # # :param code: # :param name: @@ -671,20 +675,20 @@ def create_method(self, description, link): # :param no_data_value: # :return: # """ -# var = Variable() -# var.code = code -# var.name = name -# var.speciation = speciation -# var.variable_unit_id = variable_unit_id -# var.sample_medium = sample_medium -# var.value_type = value_type -# var.is_regular = is_regular -# var.time_support = time_support -# var.time_unit_id = time_unit_id -# var.data_type = data_type -# var.general_category = general_category -# var.no_data_value = no_data_value -# + var = Variable() + var.code = code + var.name = name + var.speciation = speciation + var.variable_unit_id = variable_unit_id + var.sample_medium = sample_medium + var.value_type = value_type + var.is_regular = is_regular + var.time_support = time_support + var.time_unit_id = time_unit_id + var.data_type = data_type + var.general_category = general_category + var.no_data_value = no_data_value + self.create.createVariable(var) # self._edit_session.add(var) # self._edit_session.commit() # return var From 364597cc7f6a4741ad18a3f9a08dc749cdcc7fab Mon Sep 17 00:00:00 2001 From: sreeder Date: Thu, 10 Nov 2016 16:13:39 -0700 Subject: [PATCH 074/158] work on update formating --- ODMTools.py | 3 +- odmtools/controller/olvDataTable.py | 5 +- odmtools/controller/olvSeriesSelector.py | 2 +- odmtools/gui/pnlDataTable.py | 4 +- odmtools/lib/oldOlv/CellEditor.py | 548 --- odmtools/lib/oldOlv/Filter.py | 138 - odmtools/lib/oldOlv/ListCtrlPrinter.py | 2797 --------------- odmtools/lib/oldOlv/OLVEvent.py | 279 -- odmtools/lib/oldOlv/ObjectListView.py | 4141 ---------------------- odmtools/lib/oldOlv/README.md | 6 - odmtools/lib/oldOlv/WordWrapRenderer.py | 232 -- odmtools/lib/oldOlv/__init__.py | 65 - odmtools/lib/oldOlv/t.py | 228 -- odmtools/lib/oldOlv/timingTests.txt | 245 -- 14 files changed, 10 insertions(+), 8683 deletions(-) delete mode 100644 odmtools/lib/oldOlv/CellEditor.py delete mode 100644 odmtools/lib/oldOlv/Filter.py delete mode 100644 odmtools/lib/oldOlv/ListCtrlPrinter.py delete mode 100644 odmtools/lib/oldOlv/OLVEvent.py delete mode 100644 odmtools/lib/oldOlv/ObjectListView.py delete mode 100644 odmtools/lib/oldOlv/README.md delete mode 100644 odmtools/lib/oldOlv/WordWrapRenderer.py delete mode 100644 odmtools/lib/oldOlv/__init__.py delete mode 100644 odmtools/lib/oldOlv/t.py delete mode 100644 odmtools/lib/oldOlv/timingTests.txt diff --git a/ODMTools.py b/ODMTools.py index 331fe95..6b2014b 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -23,7 +23,8 @@ #import psycopg2 tool = LoggerTool() -logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.INFO) +# logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.INFO) +logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.DEBUG) wx.Log.SetLogLevel(0) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index e2b36b4..2925cdd 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -23,9 +23,12 @@ def __init__(self, parent, **kwargs): def init(self, memDB): self.memDB = memDB - columns = [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, + columns = \ + [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, stringConverter='%Y-%m-%d %H:%M:%S' if "date" in x.lower() else '%s') for x, i in self.memDB.getEditColumns()] + print columns + self.useAlternateBackColors = True self.oddRowsBackColor = wx.Colour(191, 217, 217) self.SetColumns(columns) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index ba3277b..9ab20db 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -2,7 +2,7 @@ import wx import wx.lib.newevent -# from ObjectListView.ObjectListView import FastObjectListView, ColumnDefn + from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn diff --git a/odmtools/gui/pnlDataTable.py b/odmtools/gui/pnlDataTable.py index 81bbc2a..b2a137c 100644 --- a/odmtools/gui/pnlDataTable.py +++ b/odmtools/gui/pnlDataTable.py @@ -87,7 +87,9 @@ def init(self, memDB): self.memDB = memDB columns = [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in x.lower() else '%s') + stringConverter= "%s" if "date" in x.lower() and "utc" not in x.lower() else '%s') + + #'%Y-%m-%d %H:%M:%S' for x, i in self.memDB.getEditColumns()] self.myOlv.useAlternateBackColors = True diff --git a/odmtools/lib/oldOlv/CellEditor.py b/odmtools/lib/oldOlv/CellEditor.py deleted file mode 100644 index aa71423..0000000 --- a/odmtools/lib/oldOlv/CellEditor.py +++ /dev/null @@ -1,548 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: CellEditor.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: -# - there has to be a better DateTimeEditor somewhere!! - -""" -The *CellEditor* module provides some editors for standard types that can be installed -in an *ObjectListView*. It also provides a *Registry* that maps between standard types -and functions that will create editors for that type. - -Cell Editors - - A cell editor can be any subclass of wx.Window provided that it supports the - following protocol: - - SetValue(self, value) - The editor should show the given value for editing - - GetValue(self) - The editor should return the value that it holds. Return None to indicate - an invalid value. The returned value should be of the correct type, i.e. - don't return a string if the editor was registered for the bool type. - - The editor should invoke FinishCellEdit() on its parent ObjectListView when it - loses focus or when the user commits the change by pressing Return or Enter. - - The editor should invoke CancelCellEdit() on its parent ObjectListView when - the user presses Escape. - -Editor Registry - - The editor registry remembers a function that will be called to create - an editor for a given type. -""" - -__author__ = "Phillip Piper" -__date__ = "3 May 2008" -__version__ = "1.0" - -import datetime -import wx - -#====================================================================== -# Editor Registry - -# Module level variable -_cellEditorRegistrySingleton = None - -def CellEditorRegistry(): - """ - Return the registry that is managing type to creator functions - """ - global _cellEditorRegistrySingleton - - if _cellEditorRegistrySingleton is None: - _cellEditorRegistrySingleton = EditorRegistry() - - return _cellEditorRegistrySingleton - - -class EditorRegistry: - """ - An *EditorRegistry* manages a mapping of types onto creator functions. - - When called, creator functions will create the appropriate kind of cell editor - """ - - def __init__(self): - self.typeToFunctionMap = {} - - # Standard types and their creator functions - self.typeToFunctionMap[str] = self._MakeStringEditor - self.typeToFunctionMap[unicode] = self._MakeStringEditor - self.typeToFunctionMap[bool] = self._MakeBoolEditor - self.typeToFunctionMap[int] = self._MakeIntegerEditor - self.typeToFunctionMap[long] = self._MakeLongEditor - self.typeToFunctionMap[float] = self._MakeFloatEditor - self.typeToFunctionMap[datetime.datetime] = self._MakeDateTimeEditor - self.typeToFunctionMap[datetime.date] = self._MakeDateEditor - self.typeToFunctionMap[datetime.time] = self._MakeTimeEditor - - # TODO: Install editors for mxDateTime if installed - - def GetCreatorFunction(self, aValue): - """ - Return the creator function that is register for the type of the given value. - Return None if there is no registered function for the type. - """ - return self.typeToFunctionMap.get(type(aValue), None) - - def RegisterCreatorFunction(self, aType, aFunction): - """ - Register the given function to be called when we need an editor for the given type. - - The function must accept three parameter: an ObjectListView, row index, and subitem index. - It should return a wxWindow that is parented on the listview, and that responds to: - - - SetValue(newValue) - - - GetValue() to return the value shown in the editor - - """ - self.typeToFunctionMap[aType] = aFunction - - #---------------------------------------------------------------------------- - # Creator functions for standard types - - @staticmethod - def _MakeStringEditor(olv, rowIndex, subItemIndex): - return BaseCellTextEditor(olv, subItemIndex) - - @staticmethod - def _MakeBoolEditor(olv, rowIndex, subItemIndex): - return BooleanEditor(olv) - - @staticmethod - def _MakeIntegerEditor(olv, rowIndex, subItemIndex): - return IntEditor(olv, subItemIndex, validator=NumericValidator()) - - @staticmethod - def _MakeLongEditor(olv, rowIndex, subItemIndex): - return LongEditor(olv, subItemIndex) - - @staticmethod - def _MakeFloatEditor(olv, rowIndex, subItemIndex): - return FloatEditor(olv, subItemIndex, validator=NumericValidator("0123456789-+eE.")) - - @staticmethod - def _MakeDateTimeEditor(olv, rowIndex, subItemIndex): - dte = DateTimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - dte.formatString = column.stringConverter - - return dte - - @staticmethod - def _MakeDateEditor(olv, rowIndex, subItemIndex): - dte = DateEditor(olv, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY | wx.WANTS_CHARS) - #dte.SetValidator(MyValidator(olv)) - return dte - - @staticmethod - def _MakeTimeEditor(olv, rowIndex, subItemIndex): - editor = TimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - editor.formatString = column.stringConverter - - return editor - -#====================================================================== -# Cell editors - - -class BooleanEditor(wx.Choice): - """This is a simple editor to edit a boolean value that can be used in an - ObjectListView""" - - def __init__(self, *args, **kwargs): - kwargs["choices"] = ["True", "False"] - wx.Choice.__init__(self, *args, **kwargs) - - def GetValue(self): - "Get the value from the editor" - return self.GetSelection() == 0 - - def SetValue(self, value): - "Put a new value into the editor" - if value: - self.Select(0) - else: - self.Select(1) - -#---------------------------------------------------------------------------- - -class BaseCellTextEditor(wx.TextCtrl): - """This is a base text editor for text-like editors used in an ObjectListView""" - - def __init__(self, olv, subItemIndex, **kwargs): - style = wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB - # Allow for odd case where parent isn't an ObjectListView - if hasattr(olv, "columns"): - if olv.HasFlag(wx.LC_ICON): - style |= (wx.TE_CENTRE | wx.TE_MULTILINE) - else: - style |= olv.columns[subItemIndex].GetAlignmentForText() - wx.TextCtrl.__init__(self, olv, style=style, **kwargs) - - # With the MULTILINE flag, the text control always has a vertical - # scrollbar, which looks stupid. I don't know how to get rid of it. - # This doesn't do it: - # self.ToggleWindowStyle(wx.VSCROLL) - -#---------------------------------------------------------------------------- - -class IntEditor(BaseCellTextEditor): - """This is a text editor for integers for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return int(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, int): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class LongEditor(BaseCellTextEditor): - """This is a text editor for long values for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return long(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, long): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class FloatEditor(BaseCellTextEditor): - """This is a text editor for floats for use in an ObjectListView. - - Because of the trouble of precisely converting floats to strings, - this editor sometimes behaves a little strangely.""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return float(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, float): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class DateTimeEditor(BaseCellTextEditor): - """ - A DateTimeEditor allows the user to enter a date/time combination, where the time is optional - and many formats of date and time are allowed. - - The control accepts these date formats (in all cases, the year can be only 2 digits): - - '31/12/2008' - - '2008/12/31' - - '12/31/2008' - - '31 December 2008' - - '31 Dec 2008' - - 'Dec 31 2008' - - 'December 31 2008' - - Slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - - The control accepts these time formats: - - '23:59:59' - - '11:59:59pm' - - '23:59' - - '11:59pm' - - '11pm' - - The colons are required. The am/pm is case insensitive. - - The implementation uses a brute force approach to parsing the data. - """ - # Acceptable formats: - # '31/12/2008', '2008/12/31', '12/31/2008', '31 December 2008', '31 Dec 2008', 'Dec 31 2007' - # second line is the same but with two-digit year. - # slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - STD_DATE_FORMATS = ['%d %m %Y', '%Y %m %d', '%m %d %Y', '%d %B %Y', '%d %b %Y', '%b %d %Y', '%B %d %Y', - '%d %m %y', '%y %m %d', '%m %d %y', '%d %B %y', '%d %b %y', '%b %d %y', '%B %d %y'] - - STD_DATE_WITHOUT_YEAR_FORMATS = ['%d %m', '%m %d', '%d %B', '%d %b', '%B %d', '%b %d'] - - # Acceptable formats: '23:59:59', '11:59:59pm', '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%H:%M:%S', '%I:%M:%S %p', '%H:%M', '%I:%M %p', '%I %p'] - - # These separators are treated as whitespace - STD_SEPARATORS = "/-," - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X %x" - - self.allDateTimeFormats = [] - for dtFmt in self.STD_DATE_FORMATS: - self.allDateTimeFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeFormats.append("%s %s" % (dtFmt, timeFmt)) - - self.allDateTimeWithoutYearFormats = [] - for dtFmt in self.STD_DATE_WITHOUT_YEAR_FORMATS: - self.allDateTimeWithoutYearFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeWithoutYearFormats.append("%s %s" % (dtFmt, timeFmt)) - - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, datetime.datetime): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - return self._ParseDateTime(s) - - - def _ParseDateTime(self, s): - # Try the installed format string first - try: - return datetime.datetime.strptime(s, self.formatString) - except ValueError: - pass - - for x in self.STD_SEPARATORS: - s = s.replace(x, " ") - - # Because of the logic of strptime, we have to check shorter patterns first. - # For example: - # "31 12" matches "%d %m %y" => datetime(2012, 1, 3, 0, 0) ?? - # but we want: - # "31 12" to match "%d %m" => datetime(1900, 12, 31, 0, 0) - # JPP 4/4/2008 Python 2.5.1 - for fmt in self.allDateTimeWithoutYearFormats: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.replace(year=datetime.datetime.today().year) - except ValueError: - pass - - for fmt in self.allDateTimeFormats: - try: - return datetime.datetime.strptime(s, fmt) - except ValueError: - pass - - return None - -#---------------------------------------------------------------------------- - -class NumericValidator(wx.PyValidator): - """This validator only accepts numeric keys""" - - def __init__(self, acceptableChars="0123456789+-"): - wx.PyValidator.__init__(self) - self.Bind(wx.EVT_CHAR, self._OnChar) - self.acceptableChars = acceptableChars - self.acceptableCodes = [ord(x) for x in self.acceptableChars] - stdEditKeys = [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE, wx.WXK_CANCEL, - wx.WXK_TAB, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_HOME, wx.WXK_END, - wx.WXK_LEFT, wx.WXK_RIGHT] - self.acceptableCodes.extend(stdEditKeys) - - def Clone(self): - "Make a new copy of this validator" - return NumericValidator(self.acceptableChars) - - def _OnChar(self, event): - "Handle the OnChar event by rejecting non-numerics" - if event.GetModifiers() != 0 and event.GetModifiers() != wx.MOD_SHIFT: - event.Skip() - return - - if event.GetKeyCode() in self.acceptableCodes: - event.Skip() - return - - wx.Bell() - -#---------------------------------------------------------------------------- - -class DateEditor(wx.DatePickerCtrl): - """ - This control uses standard datetime. - wx.DatePickerCtrl works only with wx.DateTime, but they are strange beasts. - wx.DataTime use 0 indexed months, i.e. January==0 and December==11. - """ - - def __init__(self, *args, **kwargs): - wx.DatePickerCtrl.__init__(self, *args, **kwargs) - self.SetValue(None) - - def SetValue(self, value): - if value: - dt = wx.DateTime() - dt.Set(value.day, value.month-1, value.year) - else: - dt = wx.DateTime.Today() - wx.DatePickerCtrl.SetValue(self, dt) - - def GetValue(self): - "Get the value from the editor" - dt = wx.DatePickerCtrl.GetValue(self) - if dt.IsOk(): - return datetime.date(dt.Year, dt.Month+1, dt.Day) - else: - return None - -#---------------------------------------------------------------------------- - -class TimeEditor(BaseCellTextEditor): - """A text editor that expects and return time values""" - - # Acceptable formats: '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%X', '%H:%M', '%I:%M %p', '%I %p'] - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X" - - def SetValue(self, value): - "Put a new value into the editor" - value = value or "" - if isinstance(value, datetime.time): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - fmts = self.STD_TIME_FORMATS[:] - if self.formatString not in fmts: - fmts.insert(0, self.formatString) - for fmt in fmts: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.time() - except ValueError: - pass - - return None - -#====================================================================== -# Auto complete controls - -def MakeAutoCompleteTextBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a TextCtrl that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - #THINK: We could make this time based, i.e. it escapes after 1 second. - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - tb = BaseCellTextEditor(olv, columnIndex) - AutoCompleteHelper(tb, list(options)) - return tb - -def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a ComboBox that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - cb = wx.ComboBox(olv, choices=list(options), - style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) - AutoCompleteHelper(cb) - return cb - - -#------------------------------------------------------------------------- - -class AutoCompleteHelper(object): - """ - This class operates on a text control or combobox, and automatically completes the - text typed by the user from a list of entries in a given list. - - """ - - def __init__(self, control, possibleValues=None): - self.control = control - self.lastUserEnteredString = self.control.GetValue() - self.control.Bind(wx.EVT_TEXT, self._OnTextEvent) - if isinstance(self.control, wx.ComboBox): - self.possibleValues = self.control.GetStrings() - else: - self.possibleValues = possibleValues or [] - self.lowerCasePossibleValues = [x.lower() for x in self.possibleValues] - - - def _OnTextEvent(self, evt): - evt.Skip() - # After the SetValue() we want to ignore this event. If we get this event - # and the value hasn't been modified, we know it was a SetValue() call. - if hasattr(self.control, "IsModified") and not self.control.IsModified(): - return - - # If the text has changed more than the user just typing one letter, - # then don't try to autocomplete it. - if len(evt.GetString()) != len(self.lastUserEnteredString)+1: - self.lastUserEnteredString = evt.GetString() - return - - self.lastUserEnteredString = evt.GetString() - s = evt.GetString().lower() - for i, x in enumerate(self.lowerCasePossibleValues): - if x.startswith(s): - self._AutocompleteWith(self.possibleValues[i]) - break - - - def _AutocompleteWith(self, newValue): - """Suggest the given value by autocompleting it.""" - # GetInsertionPoint() doesn't seem reliable under linux - insertIndex = len(self.control.GetValue()) - self.control.SetValue(newValue) - if isinstance(self.control, wx.ComboBox): - self.control.SetMark(insertIndex, len(newValue)) - else: - # Seems that under linux, selecting only seems to work here if we do it - # outside of the text event - wx.CallAfter(self.control.SetSelection, insertIndex, len(newValue)) diff --git a/odmtools/lib/oldOlv/Filter.py b/odmtools/lib/oldOlv/Filter.py deleted file mode 100644 index 4e2a653..0000000 --- a/odmtools/lib/oldOlv/Filter.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: Filter.py -# Author: Phillip Piper -# Created: 26 August 2008 -# Copyright: (c) 2008 Phillip Piper -# SVN-ID: $Id$ -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/26 JPP First version -#---------------------------------------------------------------------------- -# To do: -# - -""" -Filters provide a structured mechanism to display only some of the model objects -given to an ObjectListView. Only those model objects which are 'chosen' by -an installed filter will be presented to the user. - -Filters are simple callable objects which accept a single parameter, which -is the list of model objects to be filtered, and returns a collection of -those objects which will be presented to the user. - -This module provides some standard filters. - -Filters almost always impose a performance penalty on the ObjectListView. -The penalty is normally O(n) since the filter normally examines each model -object to see if it should be included. Head() and Tail() are exceptions -to this observation. -""" - -def Predicate(predicate): - """ - Display only those objects that match the given predicate - - Example:: - self.olv.SetFilter(Filter.Predicate(lambda x: x.IsOverdue())) - """ - return lambda modelObjects: [x for x in modelObjects if predicate(x)] - - -def Head(num): - """ - Display at most the first N of the model objects - - Example:: - self.olv.SetFilter(Filter.Head(1000)) - """ - return lambda modelObjects: modelObjects[:num] - - -def Tail(num): - """ - Display at most the last N of the model objects - - Example:: - self.olv.SetFilter(Filter.Tail(1000)) - """ - return lambda modelObjects: modelObjects[-num:] - - -class TextSearch(object): - """ - Return only model objects that match a given string. If columns is not empty, - only those columns will be considered when searching for the string. Otherwise, - all columns will be searched. - - Example:: - self.olv.SetFilter(Filter.TextSearch(self.olv, text="findthis")) - self.olv.RepopulateList() - """ - - def __init__(self, objectListView, columns=(), text=""): - """ - Create a filter that includes on modelObject that have 'self.text' somewhere in the given columns. - """ - self.objectListView = objectListView - self.columns = columns - self.text = text - - def __call__(self, modelObjects): - """ - Return the model objects that contain our text in one of the columns to consider - """ - if not self.text: - return modelObjects - - # In non-report views, we can only search the primary column - if self.objectListView.InReportView(): - cols = self.columns or self.objectListView.columns - else: - cols = [self.objectListView.columns[0]] - - textToFind = self.text.lower() - - def _containsText(modelObject): - for col in cols: - if textToFind in col.GetStringValue(modelObject).lower(): - return True - return False - - return [x for x in modelObjects if _containsText(x)] - - def SetText(self, text): - """ - Set the text that this filter will match. Set this to None or "" to disable the filter. - """ - self.text = text - - -class Chain(object): - """ - Return only model objects that match all of the given filters. - - Example:: - # Show at most 100 people whose salary is over 50,000 - salaryFilter = Filter.Predicate(lambda person: person.GetSalary() > 50000) - self.olv.SetFilter(Filter.Chain(salaryFilter, Filter.Tail(100))) - self.olv.RepopulateList() - """ - - def __init__(self, *filters): - """ - Create a filter that performs all the given filters. - - The order of the filters is important. - """ - self.filters = filters - - - def __call__(self, modelObjects): - """ - Return the model objects that match all of our filters - """ - for filter in self.filters: - modelObjects = filter(modelObjects) - return modelObjects diff --git a/odmtools/lib/oldOlv/ListCtrlPrinter.py b/odmtools/lib/oldOlv/ListCtrlPrinter.py deleted file mode 100644 index e1b3fe5..0000000 --- a/odmtools/lib/oldOlv/ListCtrlPrinter.py +++ /dev/null @@ -1,2797 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Inspired beginning -#---------------------------------------------------------------------------- -# To do: -# - persistence of ReportFormat -# - allow cell contents to be vertically aligned -# - in light of several of the "to dos", consider having CellBlockFormat object -# - write a data abstraction layer between the printer and the ListCtrl. -# This layer could understand ObjectListViews and be more efficient. -# - consider if this could be made to work with a wx.Grid (needs data abstraction layer) - -# Known issues: -# - the 'space' setting on decorations is not intuitive - - -""" -An ``ListCtrlPrinter`` takes an ``ObjectListView`` or ``wx.ListCtrl`` and turns it into a -pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -This will produce a report with reasonable formatting. The formatting of a report is -controlled completely by the ReportFormat object of the ListCtrlPrinter. To change the appearance -of the report, you change the settings in this object. - -A report consists of various sections (called "blocks") and each of these blocks has a -matching BlockFormat object in the ReportFormat. So, to modify the format of the -page header, you change the ReportFormat.PageHeader object. - -A ReportFormat object has the following properties which control the appearance of the matching -sections of the report: - -* PageHeader -* ListHeader -* ColumnHeader -* GroupTitle -* Row -* ListFooter -* PageFooter -* Page - -These properties return BlockFormat objects, which have the following properties: - -* AlwaysCenter -* CanWrap -* Font -* Padding -* TextAlignment -* TextColor - -* CellPadding -* GridPen - -Implementation -============== - -A ``ListCtrlPrinter`` is a *Facade* over two classes: - - ``ListCtrlPrintout``, which handles the interface to the wx printing subsystem - - ``ReportEngine``, which does the actual work of creating the report - -The ``ListCtrlPrintout`` handles all the details of the wx printing subsystem. In -particular, it configures the printing DC so that its origin and scale are correct. This -enables the ``ReportEngine`` to simply render the report without knowing the -characteristics of the underlying printer DPI, unprintable region, or the scale of a print -preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like -actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards -to the ``ReportEngine``). - -The ``ReportEngine`` uses a block structure approach to reports. Each element of -a report is a "block", which stacks vertically with other blocks. - -When a block is printed, it can either render itself into a given DC or it can replace -itself in the report structure with one or more other blocks. A ``TextBlock`` renders -itself by drawing into the DC. In contrast, the block that prints a ``ListCtrl`` replaces -itself with a ``ListHeaderBlock``, a ``ColumnHeaderBlock``, ``ListRowsBlock`` and finally a -``ListFooterBlock``. - -The blocks describe the structure of the report. The formatting of a report is -controlled by ``BlockFormat`` objects. The ``ReportFormat`` contains a ``BlockFormat`` -object for each formattable section of the report. - -Each section must be formatted in same fashion, i.e. all column headers will look the same, -all page footer will look the same. There is no way to make the first footer look one way, -and the second look a different way. -""" - -import datetime -import math - -import wx - -from WordWrapRenderer import WordWrapRenderer - -#---------------------------------------------------------------------------- - -class ListCtrlPrinter(object): - """ - An ListCtrlPrinter creates a pretty report from an ObjectListView/ListCtrl. - - """ - - def __init__(self, listCtrl=None, title="ListCtrl Printing"): - """ - """ - self.printout = ListCtrlPrintout(self) - self.engine = ReportEngine() - if listCtrl is not None: - self.AddListCtrl(listCtrl, title) - - #---------------------------------------------------------------------------- - # Accessing - - def GetPageFooter(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page footer - """ - return self.engine.pageFooter - - def SetPageFooter(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page footer. - - leftText can be a string or a 3-tuple of strings. - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageFooter = leftText - else: - self.engine.pageFooter = (leftText, centerText, rightText) - - def GetPageHeader(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page header - """ - return self.engine.pageHeader - - def SetPageHeader(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page header - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageHeader = leftText - else: - self.engine.pageHeader = (leftText, centerText, rightText) - - def GetPrintData(self): - """ - Return the wx.PrintData that controls the printing of this report - """ - return self.printout.printData - - def GetReportFormat(self): - """ - Return the ReportFormat object that controls the appearance of this printout - """ - return self.engine.reportFormat - - def SetReportFormat(self, fmt): - """ - Set the ReportFormat object that controls the appearance of this printout - """ - self.engine.reportFormat = fmt - - def GetWatermark(self, txt): - """ - Get the text that will be printed as a watermark on the report - """ - return self.engine.watermark - - def SetWatermark(self, txt): - """ - Set the text that will be printed as a watermark on the report - """ - self.engine.watermark = txt - - ReportFormat = property(GetReportFormat, SetReportFormat) - PageFooter = property(GetPageFooter, SetPageFooter) - PageHeader = property(GetPageHeader, SetPageHeader) - PrintData = property(GetPrintData) - Watermark = property(GetWatermark, SetWatermark) - - #---------------------------------------------------------------------------- - # Setup - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - self.engine.AddListCtrl(listCtrl, title) - - - def Clear(self): - """ - Remove all ListCtrls from this printer - """ - self.engine.ClearListCtrls() - - #---------------------------------------------------------------------------- - # Printing Commands - - def PageSetup(self, parent=None): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - self.printout.PageSetup(parent) - - - def PrintPreview(self, parent=None, title="ListCtrl Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - self.printout.PrintPreview(parent, title, bounds) - - - def Print(self, parent=None): - """ - Print this report to the selected printer - """ - self.printout.DoPrint(parent) - - #---------------------------------------------------------------------------- - # Callbacks - # These methods are invoked by the ListCtrlPrintout when required - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - return self.engine.CalculateTotalPages(dc, bounds) - - - def StartPrinting(self): - """ - A new print job is about to begin. - """ - self.engine.StartPrinting() - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - self.engine.PrintPage(dc, pageNumber, bounds) - -#---------------------------------------------------------------------------- - -class ReportEngine(object): - """ - A ReportEngine handles all the work of actually producing a report. - - Public instance variables (all others should be treated as private): - - * dateFormat - When the current date/time is substituted into report text, how should - the datetime be formatted? This must be a valid format string for the - strftime() method. - Default: "%x %X" - - """ - - def __init__(self): - """ - """ - self.currentPage = -1 - self.totalPages = -1 - self.blocks = list() - self.blockInsertionIndex = 0 - self.listCtrls = list() - self.dateFormat = "%x %X" - - self.reportFormat = ReportFormat.Normal() - - self.watermark = "" - self.pageHeader = list() - self.pageFooter = list() - #self.isPrintSelectionOnly = False # not currently implemented - - # If this is False, no drawing should be done. The engine is either counting - # pages, or skipping to a specific page - self.shouldDrawBlocks = True - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the given format - """ - return self.reportFormat.GetNamedFormat(name) - - - def GetTotalPages(self): - """ - Return the total number of pages that this report will produce. - - CalculateTotalPages() must be called before this is accurate. - """ - return self.totalPages - - - def GetSubstitutionInfo(self): - """ - Return a dictionary that can be used for substituting values into strings - """ - dateString = datetime.datetime.now().strftime(self.dateFormat) - info = { - "currentPage": self.currentPage, - "date": dateString, - "totalPages": self.totalPages, - } - return info - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - self.StartPrinting() - self.totalPages = 1 - self.shouldDrawBlocks = False - while self.PrintOnePage(dc, self.totalPages, bounds): - self.totalPages += 1 - self.shouldDrawBlocks = True - return self.totalPages - - #---------------------------------------------------------------------------- - # Commands - - def AddBlock(self, block): - """ - Add the given block at the current insertion point - """ - self.blocks.insert(self.blockInsertionIndex, block) - self.blockInsertionIndex += 1 - block.engine = self - - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - if listCtrl.InReportView(): - self.listCtrls.append([listCtrl, title]) - - - def ClearListCtrls(self): - """ - Remove all ListCtrls from this report. - """ - self.listCtrls = list() - - - def DropCurrentBlock(self): - """ - Remove the current block from our list of blocks - """ - self.blocks.pop(0) - self.blockInsertionIndex = 1 - - #---------------------------------------------------------------------------- - # Printing - - def StartPrinting(self): - """ - Initial a print job on this engine - """ - self.currentPage = 0 - self.blockInsertionIndex = 0 - self.blocks = list() - self.AddBlock(ReportBlock()) - self.runningBlocks = list() - self.AddRunningBlock(PageHeaderBlock()) - self.AddRunningBlock(PageFooterBlock()) - - if self.watermark: - self._CreateReplaceWatermarkDecoration() - - - def AddRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.append(block) - block.engine = self - - - def RemoveRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.remove(block) - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - # If the request page isn't next in order, we have to restart - # the printing process and advance until we reach the desired page. - if pageNumber != self.currentPage + 1: - self.StartPrinting() - self.shouldDrawBlocks = False - for i in range(1, pageNumber): - self.PrintOnePage(dc, i, bounds) - self.shouldDrawBlocks = True - - return self.PrintOnePage(dc, pageNumber, bounds) - - - def PrintOnePage(self, dc, pageNumber, bounds): - """ - Print the current page on the given device context. - - Return true if there is still more to print. - """ - # Initialize state - self.currentPage = pageNumber - self.pageBounds = list(bounds) - self.workBounds = list(self.pageBounds) - self.SubtractDecorations(dc) - - # Print page adornments, including under-text decorations - self.DrawPageDecorations(dc, False) - for x in self.runningBlocks: - x.Print(dc) - - # Print blocks until they won't fit or we run out of blocks - while len(self.blocks) and self.blocks[0].Print(dc): - self.DropCurrentBlock() - - # Finally, print over-the-text decorations - self.DrawPageDecorations(dc, True) - - return len(self.blocks) > 0 - - - def SubtractDecorations(self, dc): - """ - # Subtract the area used from the work area - """ - fmt = self.GetNamedFormat("Page") - self.workBounds = fmt.SubtractDecorations(dc, self.workBounds) - - - def DrawPageDecorations(self, dc, over): - """ - Draw the page decorations - """ - if not self.shouldDrawBlocks: - return - - fmt = self.GetNamedFormat("Page") - bounds = list(self.pageBounds) - fmt.DrawDecorations(dc, bounds, self, over) - - - def _CreateReplaceWatermarkDecoration(self): - """ - Create a watermark decoration, replacing any existing watermark - """ - pageFmt = self.GetNamedFormat("Page") - pageFmt.decorations = [x for x in pageFmt.decorations if not isinstance(x, WatermarkDecoration)] - - watermarkFmt = self.GetNamedFormat("Watermark") - pageFmt.Add(WatermarkDecoration(self.watermark, font=watermarkFmt.Font, - color=watermarkFmt.TextColor, angle=watermarkFmt.Angle, - over=watermarkFmt.Over)) - -#---------------------------------------------------------------------------- - -class ListCtrlPrintout(wx.Printout): - """ - An ListCtrlPrintout is the interface between the wx printing system - and ListCtrlPrinter. - """ - - def __init__(self, olvPrinter, margins=None): - """ - """ - wx.Printout.__init__(self) - self.olvPrinter = olvPrinter - self.margins = margins or (wx.Point(15, 15), wx.Point(15, 15)) - self.totalPages = -1 - - self.printData = wx.PrintData() - self.printData.SetPrinterName("") # Use default printer - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - """ - Return true if this printout has the given page number - """ - return page <= self.totalPages - - - def GetPageInfo(self): - """ - Return a 4-tuple indicating the ... - """ - return (1, self.totalPages, 1, 1) - - - def GetPrintPreview(self): - """ - Get a wxPrintPreview of this report - """ - data = wx.PrintDialogData(self.printData) - forViewing = ListCtrlPrintout(self.olvPrinter, self.margins) - forPrinter = ListCtrlPrintout(self.olvPrinter, self.margins) - preview = wx.PrintPreview(forViewing, forPrinter, data) - return preview - - #---------------------------------------------------------------------------- - # Commands - - def PageSetup(self, parent): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(parent, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), data.GetMarginBottomRight()) - dlg.Destroy() - - - - def PrintPreview(self, parent, title, bounds): - """ - Show a Print Preview of this report - """ - self.preview = self.GetPrintPreview() - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent): - """ - Send the report to the configured printer - """ - try: - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - finally: - pdd.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - dc = self.GetDC() - self.SetScaleAndBounds(dc) - self.totalPages = self.olvPrinter.CalculateTotalPages(dc, self.bounds) - self.olvPrinter.StartPrinting() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - return super(ListCtrlPrintout, self).OnBeginDocument(start, end) - - def OnEndDocument(self): - super(ListCtrlPrintout, self).OnEndDocument() - - def OnBeginPrinting(self): - super(ListCtrlPrintout, self).OnBeginPrinting() - - def OnEndPrinting(self): - super(ListCtrlPrintout, self).OnEndPrinting() - - def OnPrintPage(self, page): - """ - Do the work of printing the given page number. - """ - # We bounce this back to the printer facade - dc = self.GetDC() - self.SetScaleAndBounds(dc) - return self.olvPrinter.PrintPage(dc, page, self.bounds) - - def SetScaleAndBounds(self, dc): - """ - Calculate the scale required for our printout to match what appears on screen, - and the bounds that will be effective at that scale and margins - """ - # This code comes from Robin Dunn's "wxPython In Action." - # Without that, I would never have figured this out. - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logicalScale = float(ppiPrinterX) / float(ppiScreenX) - pw, ph = self.GetPageSizePixels() - dw, dh = dc.GetSize() - scale = logicalScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - - # Now calculate our boundries - logicalUnitsMM = float(ppiPrinterX) / (logicalScale*25.4) - topLeft, bottomRight = self.margins - left = round(topLeft.x * logicalUnitsMM) - top = round(topLeft.y * logicalUnitsMM) - right = round(dc.DeviceToLogicalYRel(dw) - bottomRight.x * logicalUnitsMM) - bottom = round(dc.DeviceToLogicalYRel(dh) - bottomRight.y * logicalUnitsMM) - self.bounds = (left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ReportFormat(object): - """ - A ReportFormat defines completely how a report is formatted. - - It holds a collection of BlockFormat objects which control the - formatting of individual blocks of the report - - Public instance variables: - - * IncludeImages - Should images from the ListCtrl be printed in the report? - Default: *True* - - * IsColumnHeadingsOnEachPage - Will the column headers be printed at the top of each page? - Default: *True* - - * IsShrinkToFit - Will the columns be shrunk so they all fit into the width of one page? - Default: *False* - - * UseListCtrlTextFormat - If this is True, the text format (i.e. font and text color) of each row will be taken from the ListCtrl, - rather than from the *Cell* format. - Default: *False* - - """ - - def __init__(self): - """ - """ - # Initialize the formats that control the various portions of the report - self.Page = BlockFormat() - self.PageHeader = BlockFormat() - self.ListHeader = BlockFormat() - self.GroupTitle = BlockFormat() - self.ColumnHeader = BlockFormat() - self.Row = BlockFormat() - self.ListFooter = BlockFormat() - self.PageFooter = BlockFormat() - self.Watermark = BlockFormat() - - self.IncludeImages = True - self.IsColumnHeadingsOnEachPage = False - self.IsShrinkToFit = False - self.UseListCtrlTextFormat = True - - # Initialize the watermark format to default values - self.WatermarkFormat() - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the format used in to format a block with the given name. - """ - return getattr(self, name) - - #---------------------------------------------------------------------------- - # Commands - - def WatermarkFormat(self, font=None, color=None, angle=30, over=False): - """ - Change the format of the watermark printed on this report. - - The actual text of the water mark is set by `ListCtrlPrinter.Watermark` property. - """ - defaultFaceName = "Stencil" - self.Watermark.Font = font or wx.FFont(96, wx.FONTFAMILY_DEFAULT, 0, defaultFaceName) - self.Watermark.TextColor = color or wx.Colour(204, 204, 204) - self.Watermark.Angle = angle - self.Watermark.Over = over - - #---------------------------------------------------------------------------- - # Standard formats - # These are meant to be illustrative rather than definitive - - @staticmethod - def Minimal(headerFontName="Arial", rowFontName="Times New Roman"): - """ - Return a minimal format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(18, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.GroupTitle.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.BLACK, 1, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Padding = (0, 12, 0, 12) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def Normal(headerFontName="Gill Sans", rowFontName="Times New Roman"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = True - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLUE, 2, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(26, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_LEFT - fmt.ListHeader.Background(wx.BLUE, wx.WHITE, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLUE, 4, toColor=wx.WHITE, space=5) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Background(wx.WHITE, wx.BLUE, space=(0, 4, 0, 4)) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.CellPadding = 2 - fmt.ColumnHeader.Background(wx.Colour(192, 192, 192)) - fmt.ColumnHeader.GridPen = wx.Pen(wx.WHITE, 1) - fmt.ColumnHeader.Padding = (0, 0, 0, 12) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.Line(wx.BOTTOM, pen=wx.Pen(wx.BLUE, 1, wx.DOT), space=3) - fmt.Row.CellPadding = 2 - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def TooMuch(headerFontName="Chiller", rowFontName="Gill Sans"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.PageHeader.TextColor = wx.WHITE - fmt.PageHeader.Background(wx.GREEN, wx.RED, space=(16, 4, 0, 4)) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(24, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_CENTER - fmt.ListHeader.Background(wx.RED, wx.GREEN, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.GroupTitle.TextColor = wx.BLUE - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.GREEN, 4, toColor=wx.WHITE, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.GREEN, 2, toColor=wx.RED, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Background(wx.Colour(255, 215, 0)) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.GridPen = wx.Pen(wx.Colour(192, 192, 192), 1) - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_SWISS, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.GridPen = wx.Pen(wx.BLUE, 1, wx.DOT) - fmt.Row.CanWrap = True - - fmt.Watermark.TextColor = wx.Colour(233, 150, 122) - - return fmt - -#---------------------------------------------------------------------------- - -class BlockFormat(object): - """ - A block format defines how a Block is formatted. - - These properties control the formatting of the matching Block: - - * CanWrap - If the text for this block cannot fit horizontally, should be wrap to a new line (True) - or should it be truncated (False)? - * Font - What font should be used to draw the text of this block - * Padding - How much padding should be applied to the block before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be - a collection of the paddings to be applied to the various sides: (left, top, right, bottom). - * TextAlignment - How should text be aligned within this block? Can be wx.ALIGN_LEFT, wx.ALIGN_CENTER, or - wx.ALIGN_RIGHT. - * TextColor - In what color should be text be drawn? - - The blocks that are based on cells (PageHeader, ColumnHeader, Row, PageFooter) can also - have the following properties set: - - * AlwaysCenter - Will the text in the cells be center aligned, regardless of other settings? - * CellPadding - How much padding should be applied to this cell before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be a - collection of the paddings to be applied to the various sides: (left, top, right, - bottom). - * GridPen - What Pen will be used to draw the grid lines of the cells? - - In addition to these properties, there are some methods which add various decorations to - the blocks: - - * Background(color=wx.BLUE, toColor=None, space=0) - - This gives the block a solid color background (or a gradient background if *toColor* - is not None). If *space* is not 0, *space* pixels will be subtracted from all sides - from the space available to the block. - - * Frame(pen=None, space=0) - - Draw a rectangle around the block in the given pen - - * Line(side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None) - - Draw a line on a given side of the block. If a pen is given, that is used to draw the - line (and the other parameters are ignored), otherwise a solid line (or a gradient - line is *toColor* is not None) of *width* pixels is drawn. - - """ - - def __init__(self): - """ - """ - self.padding = None - self.decorations = list() - self.font = wx.FFont(11, wx.FONTFAMILY_SWISS, face="Gill Sans") - self.textColor = None - self.textAlignment = wx.ALIGN_LEFT - self.alwaysCenter = False - self.canWrap = False - - #THINK: These attributes are only for grids. Should we have a GridBlockFormat object? - self.cellPadding = None - self.gridPen = None - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return the font used by this format - """ - return self.font - - def SetFont(self, font): - """ - Set the font used by this format - """ - self.font = font - - def GetTextAlignment(self): - """ - Return the alignment of text in this format - """ - return self.textAlignment - - def SetTextAlignment(self, alignment): - """ - Set the alignment of text in this format - """ - self.textAlignment = alignment - - def GetTextColor(self): - """ - Return the color of text in this format - """ - return self.textColor - - def SetTextColor(self, color): - """ - Set the color of text in this format - """ - self.textColor = color - - def GetPadding(self): - """ - Get the padding around this format - """ - return self.padding - - def SetPadding(self, padding): - """ - Set the padding around this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.padding = self._MakePadding(padding) - - def GetCellPadding(self): - """ - Get the padding around cells in this format - """ - return self.cellPadding - - def SetCellPadding(self, padding): - """ - Set the padding around cells in this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.cellPadding = self._MakePadding(padding) - - def GetGridPen(self): - """ - Return the pen used to draw a grid in this format - """ - return self.gridPen - - def SetGridPen(self, pen): - """ - Set the pen used to draw a grid in this format - """ - self.gridPen = pen - if self.gridPen: - # Other styles don't produce nice joins - self.gridPen.SetCap(wx.CAP_BUTT) - self.gridPen.SetJoin(wx.JOIN_MITER) - - def _MakePadding(self, padding): - try: - if len(padding) < 4: - return (tuple(padding) + (0, 0, 0, 0))[:4] - else: - return padding - except TypeError: - return (padding,) * 4 - - def GetAlwaysCenter(self): - """ - Return if the text controlled by this format should always be centered? - """ - return self.alwaysCenter - - def SetAlwaysCenter(self, value): - """ - Remember if the text controlled by this format should always be centered? - """ - self.alwaysCenter = value - - def GetCanWrap(self): - """ - Return if the text controlled by this format can wrap to cover more than one line? - """ - return self.canWrap - - def SetCanWrap(self, value): - """ - Remember if the text controlled by this format can wrap to cover more than one line? - """ - self.canWrap = value - - Font = property(GetFont, SetFont) - Padding = property(GetPadding, SetPadding) - TextAlignment = property(GetTextAlignment, SetTextAlignment) - TextColor = property(GetTextColor, SetTextColor) - CellPadding = property(GetCellPadding, SetCellPadding) - GridPen = property(GetGridPen, SetGridPen) - AlwaysCenter = property(GetAlwaysCenter, SetAlwaysCenter) - CanWrap = property(GetCanWrap, SetCanWrap) - - # Misspellers of the world Untie! - # Ok, ok... there're not really misspellings - just alternatives :) - TextAlign = property(GetTextAlignment, SetTextAlignment) - TextColour = property(GetTextColor, SetTextColor) - - #---------------------------------------------------------------------------- - # Calculations - - def CalculateCellPadding(self): - """ - Return a four-tuple that indicates pixel padding (left, top, right, bottom) - around cells in this format - """ - if self.CellPadding: - cellPadding = list(self.CellPadding) - else: - cellPadding = 0, 0, 0, 0 - - if self.GridPen: - penFactor = int((self.GridPen.GetWidth()+1)/2) - cellPadding = [x + penFactor for x in cellPadding] - - return cellPadding - - #---------------------------------------------------------------------------- - # Decorations - - def Add(self, decoration): - """ - Add the given decoration to those adorning blocks with this format - """ - self.decorations.append(decoration) - - - def Line(self, side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None): - """ - Add a line to our decorations. - If a pen is given, we use a straight Line decoration, otherwise we use a - coloured rectangle - """ - if pen: - self.Add(LineDecoration(side, pen, space)) - else: - self.Add(RectangleDecoration(side, None, color, toColor, width, space)) - - - def Background(self, color=wx.BLUE, toColor=None, space=0): - """ - Add a coloured background to the block - """ - self.Add(RectangleDecoration(color=color, toColor=toColor, space=space)) - - - def Frame(self, pen=None, space=0): - """ - Add a rectangle around this block - """ - self.Add(RectangleDecoration(pen=pen, space=space)) - - #---------------------------------------------------------------------------- - # Commands - - def SubtractPadding(self, bounds): - """ - Subtract any padding used by this format from the given bounds - """ - if self.padding is None: - return bounds - else: - return RectUtils.InsetRect(bounds, self.padding) - - - def SubtractDecorations(self, dc, bounds): - """ - Subtract any space used by our decorations from the given bounds - """ - for x in self.decorations: - bounds = x.SubtractFrom(dc, bounds) - return bounds - - - def DrawDecorations(self, dc, bounds, block, over): - """ - Draw our decorations on the given block - """ - for x in self.decorations: - if x.IsDrawOver() == over: - x.DrawDecoration(dc, bounds, block) - - -#---------------------------------------------------------------------------- - -class Block(object): - """ - A Block is a portion of a report that will stack vertically with other - Block. A report consists of several Blocks. - """ - - def __init__(self, engine=None): - self.engine = engine # This is also set when the block is added to a print engine - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - return self.GetFormat().GetTextColor() - - - def GetFormat(self): - """ - Return the BlockFormat object that controls the formatting of this block - """ - return self.engine.GetNamedFormat(self.__class__.__name__[:-5]) - - - def GetReducedBlockBounds(self, dc, bounds=None): - """ - Return the bounds of this block once padding and decoration have taken their toll. - """ - bounds = bounds or list(self.GetWorkBounds()) - fmt = self.GetFormat() - bounds = fmt.SubtractPadding(bounds) - bounds = fmt.SubtractDecorations(dc, bounds) - return bounds - - - def GetWorkBounds(self): - """ - Return the boundaries of the work area for this block - """ - return self.engine.workBounds - - - def IsColumnHeadingsOnEachPage(self): - """ - Should the column headers be report at the top of each new page? - """ - return self.engine.reportFormat.IsColumnHeadingsOnEachPage - - - def IncludeImages(self): - """ - Should the images from the ListCtrl be printed in the report? - """ - return self.engine.reportFormat.IncludeImages - - - def IsShrinkToFit(self): - """ - Should row blocks be shrunk to fit within the width of a page? - """ - return self.engine.reportFormat.IsShrinkToFit - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - This allows, for example, footers to have '%(currentPage)d of %(totalPages)d' - """ - return True - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateExtrasHeight(self, dc): - """ - Return the height of the padding and decorations themselves - """ - return 0 - RectUtils.Height(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateExtrasWidth(self, dc): - """ - Return the width of the padding and decorations themselves - """ - return 0 - RectUtils.Width(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - return -1 - - - def CalculateTextHeight(self, dc, txt, bounds=None, font=None): - """ - Return the height of the given txt in pixels - """ - bounds = bounds or self.GetReducedBlockBounds(dc) - font = font or self.GetFont() - dc.SetFont(font) - if self.GetFormat().CanWrap: - return WordWrapRenderer.CalculateHeight(dc, txt, RectUtils.Width(bounds)) - else: - # Calculate the height of one line. The 1000 pixel width - # ensures that 'Wy' doesn't wrap, which might happen if bounds is narrow - return WordWrapRenderer.CalculateHeight(dc, "Wy", 1000) - - - def CanFit(self, height): - """ - Can this block fit into the remaining work area on the page? - """ - return height <= RectUtils.Height(self.GetWorkBounds()) - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.ShouldPrint(): - return True - - bounds = self.CalculateBounds(dc) - if not self.CanFit(RectUtils.Height(bounds)): - return False - - if self.engine.shouldDrawBlocks: - self.PreDraw(dc, bounds) - self.Draw(dc, bounds) - self.PostDraw(dc, bounds) - - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - return True - - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If this block does not have a format, it is simply skipped - return self.GetFormat() is not None - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - return bounds - - - def ChangeWorkBoundsBy(self, amt): - """ - Move the top of our work bounds down by the given amount - """ - RectUtils.MoveTopBy(self.engine.workBounds, amt) - - - def Draw(self, dc, bounds): - """ - Draw this block and its decorations allowing for any padding - """ - fmt = self.GetFormat() - decorationBounds = fmt.SubtractPadding(bounds) - fmt.DrawDecorations(dc, decorationBounds, self, False) - textBounds = fmt.SubtractDecorations(dc, list(decorationBounds)) - self.DrawSelf(dc, textBounds) - fmt.DrawDecorations(dc, decorationBounds, self, True) - - - def PreDraw(self, dc, bounds): - """ - Executed before any drawing is done - """ - pass - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - pass - - - def PostDraw(self, dc, bounds): - """ - Executed after drawing has completed - """ - pass - - - def DrawText(self, dc, txt, bounds, font=None, alignment=wx.ALIGN_LEFT, valignment=wx.ALIGN_CENTRE, - image=None, color=None, canWrap=True, imageIndex=-1, listCtrl=None): - """ - Draw the given text in the given DC according to the given characteristics. - - This is the workhorse text drawing method for our reporting engine. - - The *font*, *alignment*, and *color* attributes are applied to the drawn text. - - If *image* is not None, it will be drawn to the left of the text. All text is indented - by the width of the image, even if the text is multi-line. - - If *imageIndex* is 0 or more and *listCtrl* is not None, the corresponding image from - the ListCtrl's small image list will be drawn to the left of the text. - - If *canWrap* is True, the text will be wrapped to fit within the given bounds. If it is False, - then the first line of *txt* will be truncated at the edge of the given *bounds*. - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - def _CalcBitmapPosition(r, height): - if valignment == wx.ALIGN_TOP: - return RectUtils.Top(r) - elif valignment == wx.ALIGN_CENTER: - return RectUtils.CenterY(r) - height / 2 - elif valignment == wx.ALIGN_BOTTOM: - return RectUtils.Bottom(r) - height - else: - return RectUtils.Top(r) - - # Draw any image - if image: - y = _CalcBitmapPosition(bounds, image.Height) - dc.DrawBitmap(image, RectUtils.Left(bounds), y) - RectUtils.MoveLeftBy(bounds, image.GetWidth()+GAP_BETWEEN_IMAGE_AND_TEXT) - elif listCtrl and imageIndex >=0: - imageList = listCtrl.GetImageList(wx.IMAGE_LIST_SMALL) - y = _CalcBitmapPosition(bounds, imageList.GetSize(0)[1]) - imageList.Draw(imageIndex, dc, RectUtils.Left(bounds), y, wx.IMAGELIST_DRAW_TRANSPARENT) - RectUtils.MoveLeftBy(bounds, imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT) - - # Draw the text - dc.SetFont(font or self.GetFont()) - dc.SetTextForeground(color or self.GetTextColor() or wx.BLACK) - if canWrap: - WordWrapRenderer.DrawString(dc, txt, bounds, alignment, valignment) - else: - WordWrapRenderer.DrawTruncatedString(dc, txt, bounds, alignment, valignment) - - - def PerformSubstitutions(self, strings): - """ - Substituted % markers in the given collection of strings. - """ - info = self.engine.GetSubstitutionInfo() - try: - # 'strings' could be a single string or a list of strings - try: - return strings % info - except TypeError: - return [x % info for x in strings] - except ValueError: - # We don't die if we get a substitution error - we just ignore it - return strings - -#---------------------------------------------------------------------------- - -class TextBlock(Block): - """ - A TextBlock prints a string objects. - """ - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If the block has no text, it should not be printed - if self.GetText(): - return Block.ShouldPrint(self) - else: - return False - - - def GetText(self): - return "Missing GetText() in class %s" % self.__class__.__name__ - - - def GetSubstitutedText(self): - """ - Return the text for this block after all markers have been substituted - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetText()) - else: - return self.GetText() - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - textHeight = self.CalculateTextHeight(dc, self.GetSubstitutedText()) - extras = self.CalculateExtrasHeight(dc) - return textHeight + extras - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - fmt = self.GetFormat() - self.DrawText(dc, self.GetSubstitutedText(), bounds, canWrap=fmt.CanWrap, alignment=fmt.TextAlignment) - - -#---------------------------------------------------------------------------- - -class CellBlock(Block): - """ - A CellBlock is a Block whose data is presented in a cell format. - """ - - def __init__(self): - self.scale = 1 - self.oldScale = 1 - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block. - """ - return list() - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return list() - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return list() - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return list() - - - #---------------------------------------------------------------------------- - # Accessing - - - def GetCombinedLists(self): - """ - Return a collection of Buckets that hold all the values of the - subclass-overridable collections above - """ - buckets = [Bucket(cellWidth=x, text="", align=None, image=None) for x in self.GetCellWidths()] - for (i, x) in enumerate(self.GetSubstitutedTexts()): - buckets[i].text = x - for (i, x) in enumerate(self.GetAlignments()): - buckets[i].align = x - - if self.IncludeImages(): - for (i, x) in enumerate(self.GetImages()): - buckets[i].image = x - - # Calculate where the cell contents should be drawn - cellPadding = self.GetFormat().CalculateCellPadding() - for x in buckets: - x.innerCellWidth = max(0, x.cellWidth - (cellPadding[0] + cellPadding[2])) - - return buckets - - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return None - - - def GetSubstitutedTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetTexts()) - else: - return self.GetTexts() - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - # If cells can wrap, figure out the tallest, otherwise we just figure out the height of one line - if self.GetFormat().CanWrap: - font = self.GetFont() - height = 0 - for x in self.GetCombinedLists(): - textWidth = x.innerCellWidth - if self.GetListCtrl() and x.image != -1: - imageList = self.GetListCtrl().GetImageList(wx.IMAGE_LIST_SMALL) - textWidth -= imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT - bounds = [0, 0, textWidth, 99999] - height = max(height, self.CalculateTextHeight(dc, x.text, bounds, font)) - else: - height = self.CalculateTextHeight(dc, "Wy") - - # We also have to allow for cell padding, on top of the normal padding and decorations - cellPadding = self.GetFormat().CalculateCellPadding() - return height + cellPadding[1] + cellPadding[3] + self.CalculateExtrasHeight(dc) - - - def CalculateWidth(self, dc): - """ - Calculate the total width of this block (cells plus padding) - """ - return sum(x.cellWidth for x in self.GetCombinedLists()) + self.CalculateExtrasWidth(dc) - - #---------------------------------------------------------------------------- - # Commands - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - bounds = RectUtils.SetWidth(bounds, self.CalculateWidth(dc)) - bounds = RectUtils.MultiplyOrigin(bounds, 1 / self.scale) - return bounds - - #def CanFit(self, height): - # height *= self.scale - # return Block.CanFit(self, height) - - def ChangeWorkBoundsBy(self, height): - """ - Change our workbounds by our scaled height - """ - Block.ChangeWorkBoundsBy(self, height * self.scale) - - - def PreDraw(self, dc, bounds): - """ - Apply our scale before performing any drawing - """ - self.oldScale = dc.GetUserScale() - dc.SetUserScale(self.scale * self.oldScale[0], self.scale * self.oldScale[1]) - - - def PostDraw(self, dc, bounds): - """ - Restore the scaling to what it used to be - """ - dc.SetUserScale(*self.oldScale) - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - cellFmt = self.GetFormat() - cellPadding = cellFmt.CalculateCellPadding() - combined = self.GetCombinedLists() - - # Calculate cell boundaries - cell = list(bounds) - RectUtils.SetWidth(cell, 0) - for x in combined: - RectUtils.SetLeft(cell, RectUtils.Right(cell)) - RectUtils.SetWidth(cell, x.cellWidth) - x.cell = list(cell) - - # Draw each cell - font = self.GetFont() - for x in combined: - cellBounds = RectUtils.InsetRect(x.cell, cellPadding) - self.DrawText(dc, x.text, cellBounds, font, x.align, imageIndex=x.image, - canWrap=cellFmt.CanWrap, listCtrl=self.GetListCtrl()) - - if cellFmt.GridPen and combined: - dc.SetPen(cellFmt.GridPen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - - top = RectUtils.Top(bounds) - bottom = RectUtils.Bottom(bounds) - - # Draw the interior dividers - for x in combined[:-1]: - right = RectUtils.Right(x.cell) - dc.DrawLine(right, top, right, bottom) - - # Draw the surrounding frame - left = RectUtils.Left(combined[0].cell) - right = RectUtils.Right(combined[-1].cell) - dc.DrawRectangle(left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ThreeCellBlock(CellBlock): - """ - A ThreeCellBlock divides its space evenly into three cells. - """ - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block - """ - # We divide the available space between the non-empty cells - numNonEmptyTexts = sum(1 for x in self.GetTexts() if x) - if not numNonEmptyTexts: - return (0, 0, 0) - - widths = list() - width = round(RectUtils.Width(self.GetWorkBounds()) / numNonEmptyTexts) - for x in self.GetTexts(): - if x: - widths.append(width) - else: - widths.append(0) - return widths - - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return (wx.ALIGN_LEFT, wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_RIGHT) - -#---------------------------------------------------------------------------- - -class ReportBlock(Block): - """ - A ReportBlock is boot strap Block that represents an entire report. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.engine.listCtrls: - return True - - # Print each ListView. Each list but the first starts on a separate page - self.engine.AddBlock(ListBlock(*self.engine.listCtrls[0])) - for (lv, title) in self.engine.listCtrls[1:]: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListBlock(lv, title)) - - return True - - -#---------------------------------------------------------------------------- - -class PageHeaderBlock(ThreeCellBlock): - """ - A PageHeaderBlock appears at the top of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageHeader - - - -#---------------------------------------------------------------------------- - -class PageFooterBlock(ThreeCellBlock): - """ - A PageFooterBlock appears at the bottom of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageFooter - - - #---------------------------------------------------------------------------- - # Printing - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - # Draw the footer at the bottom of the page - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - return [RectUtils.Left(bounds), RectUtils.Bottom(bounds) - height, - RectUtils.Width(bounds), height] - - - def ChangeWorkBoundsBy(self, height): - """ - The footer changes the bottom of the work bounds - """ - RectUtils.MoveBottomBy(self.engine.workBounds, -height) - - -#---------------------------------------------------------------------------- - -class PageBreakBlock(Block): - """ - A PageBreakBlock acts a page break. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - # Completely fill the remaining area on the page, forcing a page break - bounds = self.GetWorkBounds() - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - - return True - -#---------------------------------------------------------------------------- - -class RunningBlockPusher(Block): - """ - A RunningBlockPusher pushes (or pops) a running block onto the stack when it is executed. - """ - - def __init__(self, block, push=True): - """ - """ - self.block = block - self.push = push - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if self.push: - self.engine.AddRunningBlock(self.block) - else: - self.engine.RemoveRunningBlock(self.block) - - return True - -#---------------------------------------------------------------------------- - -class ListBlock(Block): - """ - A ListBlock handles the printing of an entire ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - cellWidths = self.CalculateCellWidths() - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - # Break the list into vertical slices. Each one but the first will be placed on a - # new page. - first = True - for (left, right) in self.CalculateSlices(boundsWidth, cellWidths): - if not first: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListHeaderBlock(self.lv, self.title)) - self.engine.AddBlock(ListSliceBlock(self.lv, left, right, cellWidths)) - self.engine.AddBlock(ListFooterBlock(self.lv, "")) - first = False - - return True - - - def CalculateCellWidths(self): - """ - Return a list of the widths of the cells in this lists - """ - columnHeaderFmt = self.engine.GetNamedFormat("ColumnHeader") - cellPadding = columnHeaderFmt.CalculateCellPadding() - padding = cellPadding[0] + cellPadding[2] - return [self.lv.GetColumnWidth(i) + padding for i in range(self.lv.GetColumnCount())] - - - def CalculateSlices(self, maxWidth, columnWidths): - """ - Return a list of integer pairs, where each pair represents - the left and right columns that can fit into the width of one page - """ - firstColumn = 0 - - # If a GroupListView has a column just for the expand/collapse, don't include it - if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: - firstColumn = 1 - - # If we are shrinking to fit or all the columns fit, just return all columns - if self.IsShrinkToFit() or (sum(columnWidths)) <= maxWidth: - return [ [firstColumn, len(columnWidths)-1] ] - - pairs = list() - left = firstColumn - right = firstColumn - while right < len(columnWidths): - if (sum(columnWidths[left:right+1])) > maxWidth: - if left == right: - pairs.append([left, right]) - left += 1 - right += 1 - else: - pairs.append([left, right-1]) - left = right - else: - right += 1 - - if left < len(columnWidths): - pairs.append([left, right-1]) - - return pairs - - -#---------------------------------------------------------------------------- - -class ListHeaderBlock(TextBlock): - """ - A ListHeaderBlock is the title that comes before an ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.title - -#---------------------------------------------------------------------------- - -class ListFooterBlock(TextBlock): - """ - A ListFooterBlock is the text that comes after an ListCtrl. - """ - - def __init__(self, lv, text): - self.lv = lv - self.text = text - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.text - - -#---------------------------------------------------------------------------- - -class GroupTitleBlock(TextBlock): - """ - A GroupTitleBlock is the title that comes before a list group. - """ - - def __init__(self, lv, group): - self.lv = lv - self.group = group - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.group.title - -#---------------------------------------------------------------------------- - -class ListSliceBlock(Block): - """ - A ListSliceBlock prints a vertical slice of an ListCtrl. - """ - - def __init__(self, lv, left, right, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # If we are shrinking our cells, calculate the shrink factor - if self.IsShrinkToFit(): - scale = self.CalculateShrinkToFit(dc) - else: - scale = 1 - - headerBlock = ColumnHeaderBlock(self.lv, self.left, self.right, scale, self.allCellWidths) - self.engine.AddBlock(headerBlock) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock)) - - # Are we printing a GroupListView? - # We can't use isinstance() since ObjectListView may not be installed - if hasattr(self.lv, "GetShowGroups") and self.lv.GetShowGroups(): - self.engine.AddBlock(GroupListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - else: - self.engine.AddBlock(ListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock, False)) - - return True - - def CalculateShrinkToFit(self, dc): - """ - How much do we have to shrink our rows by to fit onto the page? - """ - block = ColumnHeaderBlock(self.lv, self.left, self.right, 1, self.allCellWidths) - block.engine = self.engine - rowWidth = block.CalculateWidth(dc) - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - if rowWidth > boundsWidth: - return float(boundsWidth) / float(rowWidth) - else: - return 1 - -#---------------------------------------------------------------------------- - -class ColumnBasedBlock(CellBlock): - """ - A ColumnBasedBlock is a cell block that takes its width from the - columns of a ListCtrl. - - This is an abstract class - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return self.lv - - - def GetCellWidths(self): - """ - Return the widths of the cells used in this block - """ - #return [self.allCellWidths[i] for i in range(self.left, self.right+1)] - return self.allCellWidths[self.left:self.right+1] - - #---------------------------------------------------------------------------- - # Utiltities - - def GetColumnAlignments(self, lv, left, right): - """ - Return the alignments of the given slice of columns - """ - listAlignments = [lv.GetColumn(i).GetAlign() for i in range(left, right+1)] - mapping = { - wx.LIST_FORMAT_LEFT: wx.ALIGN_LEFT, - wx.LIST_FORMAT_RIGHT: wx.ALIGN_RIGHT, - wx.LIST_FORMAT_CENTRE: wx.ALIGN_CENTRE, - } - return [mapping[x] for x in listAlignments] - - - -#---------------------------------------------------------------------------- - -class ColumnHeaderBlock(ColumnBasedBlock): - """ - A ColumnHeaderBlock prints a portion of the columns header in a ListCtrl. - """ - - #---------------------------------------------------------------------------- - # Accessing - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetColumn(i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - if self.GetFormat().AlwaysCenter: - return [wx.ALIGN_CENTRE for i in range(self.left, self.right+1)] - else: - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - # For some reason, columns return 0 when they have no image, rather than -1 like they should - images = list() - for i in range(self.left, self.right+1): - imageIndex = self.lv.GetColumn(i).GetImage() - if imageIndex == 0: - imageIndex = -1 - images.append(imageIndex) - return images - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - -#---------------------------------------------------------------------------- - -class ListRowsBlock(Block): - """ - A ListRowsBlock prints rows of an ListCtrl. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex < self.totalRows: - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - -#---------------------------------------------------------------------------- - -class GroupListRowsBlock(Block): - """ - A GroupListRowsBlock prints rows of an GroupListView. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv # Must be a GroupListView - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex >= self.totalRows: - return True - - # If GetObjectAt() return an object, then it's a normal row. - # Otherwise, if the innerList object isn't None, it must be a ListGroup - # We can't use isinstance(x, GroupListView) because ObjectListView may not be installed - if self.lv.GetObjectAt(self.currentIndex): - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - elif self.lv.innerList[self.currentIndex]: - self.engine.AddBlock(GroupTitleBlock(self.lv, self.lv.innerList[self.currentIndex])) - - # Schedule the printing of the remaining rows - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - - -#---------------------------------------------------------------------------- - -class RowBlock(ColumnBasedBlock): - """ - A RowBlock prints a vertical slice of a single row of an ListCtrl. - """ - - def __init__(self, lv, rowIndex, left, right, scale, allCellWidths): - self.lv = lv - self.rowIndex = rowIndex - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - font = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what font is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasFont(): - font = attr.GetFont() - else: - font = listCtrl.GetItemFont(self.rowIndex) - - if font and font.IsOk(): - return font - else: - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - color = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what text colour is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasTextColour(): - color = attr.GetTextColour() - else: - color = listCtrl.GetItemTextColour(self.rowIndex) - - if color and color.IsOk(): - return color - else: - return self.GetFormat().GetTextColor() - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - #---------------------------------------------------------------------------- - # Overrides for CellBlock - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetItem(self.rowIndex, i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return [self.lv.GetItem(self.rowIndex, i).GetImage() for i in range(self.left, self.right+1)] - - #def GetTexts(self): - # """ - # Return a list of the texts that should be drawn with the cells - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetStringValueAt(modelObjects, i) for i in range(self.left, self.right+1)] - # - #def GetAlignments(self): - # """ - # Return a list indicating how the text within each cell is aligned. - # """ - # return [self.lv.columns[i].GetAlignment() for i in range(self.left, self.right+1)] - # - #def GetImages(self): - # """ - # Return a list of the images that should be drawn in each cell - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetImageAt(modelObjects, i) for i in range(self.left, self.right+1)] - - -#---------------------------------------------------------------------------- - -class Decoration(object): - """ - A Decoration add some visual effect to a Block (e.g. borders, - background image, watermark). They can also reserve a chunk of their Blocks - space for their own use. - - Decorations are added to a BlockFormat which is then applied to a ReportBlock. - - All the decorations for a block are drawn into the same area. If two decorations are - added, they will draw over the top of each other. This is normally what is expected, - but may sometimes be surprising. For example, if you add two Lines to the left of the - same block, they will draw over the top of each other. - - """ - - #---------------------------------------------------------------------------- - # Accessing - - def IsDrawOver(self): - """ - Return True if this decoration should be drawn over it's block. If not, - it will be drawn underneath - """ - return False - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - return bounds - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - pass - -#---------------------------------------------------------------------------- - -class RectangleDecoration(Decoration): - """ - A RectangleDecoration draw a rectangle around or on the side of a block. - - The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. - - """ - - def __init__(self, side=None, pen=None, color=None, toColor=None, width=0, space=0): - """ - If color is None, the rectangle will be hollow. - If toColor is None, the rectangle will be filled with "color" otherwise it will be - gradient-filled. - If pen is not None, the rectangle will be framed with that pen. - If no side is given, the rectangle will be drawn around the block. In that case, - space can be a list giving the space on each side. - """ - self.side = side - self.pen = pen - self.color = color - self.toColor = toColor - self.width = width - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - if self.side is None: - return RectUtils.InsetBy(RectUtils.InsetBy(bounds, self.space), self.width) - - inset = self.space + self.width - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - rect = self._CalculateRect(bounds) - - if self.color: - if self.toColor is None: - dc.SetPen(wx.TRANSPARENT_PEN) - dc.SetBrush(wx.Brush(self.color)) - dc.DrawRectangle(*rect) - else: - dc.GradientFillLinear(wx.Rect(*rect), self.color, self.toColor) - - if self.pen: - dc.SetPen(self.pen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.DrawRectangle(*rect) - - - def _CalculateRect(self, bounds): - """ - Calculate the rectangle that this decoration is going to paint - """ - if self.side is None: - return bounds - if self.side == wx.LEFT: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.RIGHT: - return (RectUtils.Right(bounds) - self.width, RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.TOP: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), RectUtils.Width(bounds), self.width) - if self.side == wx.BOTTOM: - return (RectUtils.Left(bounds), RectUtils.Bottom(bounds) - self.width, RectUtils.Width(bounds), self.width) - - return bounds - -#---------------------------------------------------------------------------- - -class LineDecoration(Decoration): - """ - A LineDecoration draws a line on the side of a decoration. - """ - - def __init__(self, side=wx.BOTTOM, pen=None, space=0): - self.side = side - self.pen = pen - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - inset = self.space - if self.pen is not None: - inset += self.pen.GetWidth() - - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - if self.pen == None: - return - - if self.side == wx.LEFT: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.BottomLeft(bounds) - elif self.side == wx.RIGHT: - pt1 = RectUtils.TopRight(bounds) - pt2 = RectUtils.BottomRight(bounds) - elif self.side == wx.TOP: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.TopRight(bounds) - elif self.side == wx.BOTTOM: - pt1 = RectUtils.BottomLeft(bounds) - pt2 = RectUtils.BottomRight(bounds) - - dc.SetPen(self.pen) - dc.DrawLine(pt1[0], pt1[1], pt2[0], pt2[1]) - -#---------------------------------------------------------------------------- - -class WatermarkDecoration(Decoration): - """ - A WatermarkDecoration draws an angled line of text over each page. - - The watermark is rotated around the center of the page. - - If *over* is True, the watermark will be printed on top of the page. - Otherwise, it will be printed under the page. - - """ - - def __init__(self, text, font=None, color=None, angle=30, over=True): - """ - """ - self.text = text - self.color = color or wx.Color(128, 128, 128, 128) - self.font = font or wx.FFont(128, wx.SWISS, 0) - self.angle = angle - self.over = over - - def IsDrawOver(self): - """ - Should this decoration be drawn over the rest of page? - """ - return self.over - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - dc.SetFont(self.font) - dc.SetTextForeground(self.color) - - # Rotate the text around the center of the page - cx, cy = RectUtils.Center(bounds) - w, h = dc.GetTextExtent(self.text) - - x = cx - w/2 - y = cy - h/2 + (w/2 * math.sin(math.radians(self.angle))) - - dc.DrawRotatedText(self.text, x, y, self.angle) - -#---------------------------------------------------------------------------- - -class ImageDecoration(Decoration): - """ - A ImageDecoration draws an image over (or under) the given block. - - NB: Printer contexts do not honor alpha channels. - """ - - def __init__(self, image=None, horizontalAlign=wx.CENTER, verticalAlign=wx.CENTER, over=True): - """ - image must be either an wx.Image or a wx.Bitmap - """ - self.horizontalAlign = horizontalAlign - self.verticalAlign = verticalAlign - self.over = True - - self.bitmap = image - if isinstance(image, wx.Image): - self.bitmap = wx.BitmapFromImage(image) - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - if not self.bitmap: - return - - if self.horizontalAlign == wx.LEFT: - x = RectUtils.Left(bounds) - elif self.horizontalAlign == wx.RIGHT: - x = RectUtils.Right(bounds) - self.bitmap.Width - else: - x = RectUtils.CenterX(bounds) - self.bitmap.Width/2 - - if self.verticalAlign == wx.TOP: - y = RectUtils.Top(bounds) - elif self.verticalAlign == wx.BOTTOM: - y = RectUtils.Bottom(bounds) - self.bitmap.Height - else: - y = RectUtils.CenterY(bounds) - self.bitmap.Height/2 - - dc.DrawBitmap(self.bitmap, x, y, True) - - -#---------------------------------------------------------------------------- - -class Bucket(object): - """ - General purpose, hold-all data object - """ - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - strs = ["%s=%r" % kv for kv in self.__dict__.items()] - return "Bucket(" + ", ".join(strs) + ")" - -#---------------------------------------------------------------------------- - -class RectUtils: - """ - Static rectangle utilities. - - Rectangles are a list or tuple of 4-elements: [left, top, width, height] - """ - - #---------------------------------------------------------------------------- - # Accessing - - @staticmethod - def Left(r): return r[0] - - @staticmethod - def Top(r): return r[1] - - @staticmethod - def Width(r): return r[2] - - @staticmethod - def Height(r): return r[3] - - @staticmethod - def Right(r): return r[0] + r[2] - - @staticmethod - def Bottom(r): return r[1] + r[3] - - @staticmethod - def TopLeft(r): return [r[0], r[1]] - - @staticmethod - def TopRight(r): return [r[0] + r[2], r[1]] - - @staticmethod - def BottomLeft(r): return [r[0], r[1] + r[3]] - - @staticmethod - def BottomRight(r): return [r[0] + r[2], r[1] + r[3]] - - @staticmethod - def CenterX(r): return r[0] + r[2]/2 - - @staticmethod - def CenterY(r): return r[1] + r[3]/2 - - @staticmethod - def Center(r): return [r[0] + r[2]/2, r[1] + r[3]/2] - - #---------------------------------------------------------------------------- - # Modifying - - @staticmethod - def SetLeft(r, l): - r[0] = l - return r - - @staticmethod - def SetTop(r, t): - r[1] = t - return r - - @staticmethod - def SetWidth(r, w): - r[2] = w - return r - - @staticmethod - def SetHeight(r, h): - r[3] = h - return r - - @staticmethod - def MoveLeftBy(r, delta): - r[0] += delta - r[2] -= delta - return r - - @staticmethod - def MoveTopBy(r, delta): - r[1] += delta - r[3] -= delta - return r - - @staticmethod - def MoveRightBy(r, delta): - r[2] += delta - return r - - @staticmethod - def MoveBottomBy(r, delta): - r[3] += delta - return r - - #---------------------------------------------------------------------------- - # Calculations - - @staticmethod - def InsetBy(r, delta): - if delta is None: - return r - try: - delta[0] # is it indexable? - return RectUtils.InsetRect(r, delta) - except: - return RectUtils.InsetRect(r, (delta, delta, delta, delta)) - - @staticmethod - def InsetRect(r, r2): - if r2 is None: - return r - else: - return [r[0] + r2[0], r[1] + r2[1], r[2] - (r2[0] + r2[2]), r[3] - (r2[1] + r2[3])] - - @staticmethod - def MultiplyOrigin(r, factor): - return [r[0]*factor, r[1]*factor, r[2], r[3]] - -#---------------------------------------------------------------------------- -# TESTING ONLY -#---------------------------------------------------------------------------- - -if __name__ == '__main__': - import wx - from ObjectListView import ObjectListView, FastObjectListView, GroupListView, ColumnDefn - - # Where can we find the Example module? - import sys - sys.path.append("../Examples") - - import ExampleModel - import ExampleImages - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - #self.lv = ObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - self.lv = GroupListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - #self.lv = FastObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.lv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - musicImage = self.lv.AddImages(ExampleImages.getMusic16Bitmap(), ExampleImages.getMusic32Bitmap()) - artistImage = self.lv.AddImages(ExampleImages.getUser16Bitmap(), ExampleImages.getUser32Bitmap()) - - self.lv.SetColumns([ - ColumnDefn("Title", "left", 200, "title", imageGetter=musicImage), - ColumnDefn("Artist", "left", 150, "artist", imageGetter=artistImage), - ColumnDefn("Last Played", "left", 100, "lastPlayed"), - ColumnDefn("Size", "center", 100, "sizeInBytes"), - ColumnDefn("Rating", "center", 100, "rating"), - ]) - - #self.lv.CreateCheckStateColumn() - self.lv.SetSortColumn(self.lv.columns[2]) - self.lv.SetObjects(ExampleModel.GetTracks()) - - wx.CallLater(50, self.run) - - def run(self): - printer = ListCtrlPrinter(self.lv, "Playing with ListCtrl Printing") - printer.ReportFormat = ReportFormat.Normal() - printer.ReportFormat.WatermarkFormat(over=True) - printer.ReportFormat.IsColumnHeadingsOnEachPage = True - - #printer.ReportFormat.Page.Add(ImageDecoration(ExampleImages.getGroup32Bitmap(), wx.RIGHT, wx.BOTTOM)) - - #printer.PageHeader("%(listTitle)s") # nice idea but not possible at the moment - printer.PageHeader = "Playing with ListCtrl Printing" - printer.PageFooter = ("Bright Ideas Software", "%(date)s", "%(currentPage)d of %(totalPages)d") - printer.Watermark = "Sloth!" - - #printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/OLVEvent.py b/odmtools/lib/oldOlv/OLVEvent.py deleted file mode 100644 index a447cb4..0000000 --- a/odmtools/lib/oldOlv/OLVEvent.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: OLVEvent.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/18 JPP Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events -# 2008/07/16 JPP Added group-related events -# 2008/06/19 JPP Added EVT_SORT -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: - -""" -The OLVEvent module holds all the events used by the ObjectListView module. -""" - -__author__ = "Phillip Piper" -__date__ = "3 August 2008" -__version__ = "1.1" - -import wx - -#====================================================================== -# Event ids and types - -def _EventMaker(): - evt = wx.NewEventType() - return (evt, wx.PyEventBinder(evt)) - -(olv_EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTING) = _EventMaker() -(olv_EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_STARTED) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHING) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHED, EVT_CELL_EDIT_FINISHED) = _EventMaker() -(olv_EVT_SORT, EVT_SORT) = _EventMaker() -(olv_EVT_GROUP_CREATING, EVT_GROUP_CREATING) = _EventMaker() -(olv_EVT_GROUP_SORT, EVT_GROUP_SORT) = _EventMaker() -(olv_EVT_EXPANDING, EVT_EXPANDING) = _EventMaker() -(olv_EVT_EXPANDED, EVT_EXPANDED) = _EventMaker() -(olv_EVT_COLLAPSING, EVT_COLLAPSING) = _EventMaker() -(olv_EVT_COLLAPSED, EVT_COLLAPSED) = _EventMaker() - -#====================================================================== -# Event parameter blocks - -class VetoableEvent(wx.PyCommandEvent): - """ - Base class for all cancellable actions - """ - - def __init__(self, evtType): - wx.PyCommandEvent.__init__(self, evtType, -1) - self.veto = False - - def Veto(self, isVetoed=True): - """ - Veto (or un-veto) this event - """ - self.veto = isVetoed - - def IsVetoed(self): - """ - Has this event been vetod? - """ - return self.veto - -#---------------------------------------------------------------------------- - -class CellEditEvent(VetoableEvent): - """ - Base class for all cell editing events - """ - - def SetParameters(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor): - self.objectListView = objectListView - self.rowIndex = rowIndex - self.subItemIndex = subItemIndex - self.rowModel = rowModel - self.cellValue = cellValue - self.editor = editor - -#---------------------------------------------------------------------------- - -class CellEditStartedEvent(CellEditEvent): - """ - A cell has started to be edited. - - All attributes are public and should be considered read-only. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - -#---------------------------------------------------------------------------- - -class CellEditStartingEvent(CellEditEvent): - """ - A cell is about to be edited. - - All attributes are public and should be considered read-only. Methods are provided for - information that can be changed. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - self.newEditor = None - self.shouldConfigureEditor = True - - def SetCellBounds(self, rect): - """ - Change where the editor will be placed. - rect is a list: [left, top, width, height] - """ - self.cellBounds = rect - - def SetNewEditor(self, control): - """ - Use the given control instead of the editor. - """ - self.newEditor = control - - def DontConfigureEditor(self): - """ - The editor will not be automatically configured. - - If this is called, the event handler must handle all configuration. In - particular, it must configure its own event handlers to that - ObjectListView.CancelCellEdit() is called when the user presses Escape, - and ObjectListView.CommitCellEdit() is called when the user presses - Enter/Return or when the editor loses focus. """ - self.shouldConfigureEditor = False - -#---------------------------------------------------------------------------- - -class CellEditFinishedEvent(CellEditEvent): - """ - The user has finished editing a cell. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, None, None) - self.userCancelled = userCancelled - -#---------------------------------------------------------------------------- - -class CellEditFinishingEvent(CellEditEvent): - """ - The user is finishing editing a cell. - - If this event is vetoed, the edit will be cancelled silently. This is useful if the - event handler completely handles the model updating. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.userCancelled = userCancelled - - def SetCellValue(self, value): - """ - If the event handler sets the cell value here, this value will be used to update the model - object, rather than the value that was actually in the cell editor - """ - self.cellValue = value - -#---------------------------------------------------------------------------- - -class SortEvent(VetoableEvent): - """ - The user wants to sort the ObjectListView. - - When sortModelObjects is True, the event handler should sort the model objects used by - the given ObjectListView. If the "modelObjects" instance variable is not None, that - collection of objects should be sorted, otherwise the "modelObjects" collection of the - ObjectListView should be sorted. For a VirtualObjectListView, "modelObjects" will - always be None and the programmer must sort the object in whatever backing store is - being used. - - When sortModelObjects is False, the event handler must sort the actual ListItems in - the OLV. It does this by calling SortListItemsBy(), passing a callable that accepts - two model objects as parameters. sortModelObjects must be True for a - VirtualObjectListView (or a FastObjectListView) since virtual lists cannot sort items. - - If the handler calls Veto(), no further default processing will be done. - If the handler calls Handled(), default processing concerned with UI will be done. This - includes updating sort indicators. - If the handler calls neither of these, all default processing will be done. - """ - def __init__(self, objectListView, sortColumnIndex, sortAscending, sortModelObjects, modelObjects=None): - VetoableEvent.__init__(self, olv_EVT_SORT) - self.objectListView = objectListView - self.sortColumnIndex = sortColumnIndex - self.sortAscending = sortAscending - self.sortModelObjects = sortModelObjects - self.modelObjects = modelObjects - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the ObjectListView. - The OLV will handle other tasks like updating sort indicators - """ - self.wasHandled = wasHandled - -#---------------------------------------------------------------------------- - -class GroupCreationEvent(wx.PyCommandEvent): - """ - The user is about to create one or more groups. - - The handler can mess with the list of groups before they are created: change their - names, give them icons, remove them from the list to stop them being created - (that last behaviour could be very confusing for the users). - """ - def __init__(self, objectListView, groups): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_CREATING, -1) - self.objectListView = objectListView - self.groups = groups - -#---------------------------------------------------------------------------- - -class ExpandCollapseEvent(VetoableEvent): - """ - The user wants to expand or collapse one or more groups, or has just done so. - - If the handler calls Veto() for a Expanding or Collapsing event, - the expand/collapse action will be cancelled. - - Calling Veto() has no effect on a Expanded or Collapsed event - """ - def __init__(self, eventType, objectListView, groups, isExpand): - VetoableEvent.__init__(self, eventType) - self.objectListView = objectListView - self.groups = groups - self.isExpand = isExpand - -def ExpandingCollapsingEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDING, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSING, objectListView, groups, False) - -def ExpandedCollapsedEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDED, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSED, objectListView, groups, False) - -#---------------------------------------------------------------------------- - -class SortGroupsEvent(wx.PyCommandEvent): - """ - The given list of groups needs to be sorted. - - Both the groups themselves and the model objects within the group should be sorted. - - The handler should rearrange the list of groups in the order desired. - """ - def __init__(self, objectListView, groups, sortColumn, sortAscending): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_SORT, -1) - self.objectListView = objectListView - self.groups = groups - self.sortColumn = sortColumn - self.sortAscending = sortAscending - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the groups. - """ - self.wasHandled = wasHandled diff --git a/odmtools/lib/oldOlv/ObjectListView.py b/odmtools/lib/oldOlv/ObjectListView.py deleted file mode 100644 index 6e13ea4..0000000 --- a/odmtools/lib/oldOlv/ObjectListView.py +++ /dev/null @@ -1,4141 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: ObjectListView.py -# Author: Phillip Piper -# Created: 29 February 2008 -# Copyright: (c) 2008 Phillip Piper -# SVN-ID: $Id$ -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/09/02 JPP - Added BatchedUpdate adaptor -# - Improved speed of selecting and refreshing by keeping a map -# of objects to indicies -# - Added GetIndexOf() -# - Removed flicker from FastObjectListView.AddObjects() and RefreshObjects() -# 2008/08/27 JPP - Implemented filtering -# - Added GetObjects() and GetFilteredObjects() -# - Added resortNow parameter to SetSortColumn() -# 2008/08/23 JPP - Added AddObjects()/RemoveObjects() and friends -# - Removed duplicate code when building/refreshing/adding objects -# - One step closer to secondary sort column support -# 2008/08/18 JPP - Handle model objects that cannot be hashed -# - Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events -# 2008/08/16 JPP - Added ensureVisible parameter to SelectObject() -# 2008/08/05 JPP - GroupListView is now implemented as a virtual list. Much faster! -# v1.1 -# 2008/07/19 JPP - Added GroupListView -# - Broke common virtual list behaviour into AbstractVirtualListView -# 2008/07/13 JPP - Added CopySelectionToClipboard and CopyObjectsToClipboard -# 2008/07/08 JPP - Fixed several Linux specific bugs/limits -# 2008/07/03 JPP - Allow headers to have images -# v1.0.1 -# 2008/06/22 JPP - Allowed for custom sorting, even on virtual lists -# - Fixed bug where an imageGetter that returned 0 was treated -# as if it returned -1 (i.e. no image) -# 2008/06/17 JPP - Use binary searches when searching on sorted columns -# 2008/06/16 JPP - Search by sorted column works, even on virtual lists -# 2008/06/12 JPP - Added sortable parameter -# - Renamed sortColumn to be sortColumnIndex to make it clear -# - Allow returns in multiline cell editors -# v1.0 -# 2008/05/29 JPP Used named images internally -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/05/24 JPP Images can be referenced by name -# 2008/05/17 JPP Checkboxes supported -# 2008/04/18 JPP Cell editing complete -# 2008/03/31 JPP Added space filling columns -# 2008/03/29 JPP Added minimum, maximum and fixed widths for columns -# 2008/03/22 JPP Added VirtualObjectListView and FastObjectListView -# 2008/02/29 JPP Version converted from wax -# 2006/11/03 JPP First version under wax -#---------------------------------------------------------------------------- -# To do: -# - selectable columns, triggered on right click on header -# - secondary sort column -# - optionally preserve selection on RepopulateList -# - get rid of scrollbar when editing label in icon view -# - need a ChangeView() method to help when switching between views - -""" -An `ObjectListView` provides a more convienent and powerful interface to a ListCtrl. - -The major features of an `ObjectListView` are: - - * Automatically transforms a collection of model objects into a ListCtrl. - * Automatically sorts rows by their data type. - * Easily edits the values shown in the ListCtrl. - * Supports all ListCtrl views (report, list, large and small icons). - * Columns can be fixed-width, have a minimum and/or maximum width, or be space-filling. - * Displays a "list is empty" message when the list is empty (obviously). - * Supports custom formatting of rows - * Supports alternate rows background colors. - * Supports checkbox columns - * Supports searching (by typing) on the sorted column -- even on virtual lists. - * Supports filtering of rows - * The `FastObjectListView` version can build a list of 10,000 objects in less than 0.1 seconds. - * The `VirtualObjectListView` version supports millions of rows through ListCtrl's virtual mode. - * The `GroupListView` version partitions it's rows into collapsible groups. - -An `ObjectListView` works in a declarative manner: the programmer configures how it should -work, then gives it the list of objects to display. The primary configuration is in the -definitions of the columns. Columns are configured to know which aspect of their model -they should display, how it should be formatted, and even how new values should be written -back into the model. See `ColumnDefn` for more information. - -""" - -__author__ = "Phillip Piper" -__date__ = "18 June 2008" -__version__ = "1.1" - -import wx -import datetime -import itertools -import locale -import operator -import string -import time - -import CellEditor -import OLVEvent - - -class ObjectListView(wx.ListCtrl): - """ - An object list displays various aspects of a list of objects in a multi-column list control. - - To use an ObjectListView, the programmer defines what columns are in the control and which - bits of information each column should display. The programmer then calls `SetObjects` with - the list of objects that the ObjectListView should display. The ObjectListView then builds - the control. - - Columns hold much of the intelligence of this control. Columns define both the format - (width, alignment), the aspect to be shown in the column, and the columns behaviour. - See `ColumnDefn` for full details. - - These are public instance variables. (All other variables should be considered private.) - - * cellEditMode - This control whether and how the cells of the control are editable. It can be - set to one of the following values: - - CELLEDIT_NONE - Cell editing is not allowed on the control This is the default. - - CELLEDIT_SINGLECLICK - Single clicking on any subitem cell begins an edit operation on that cell. - Single clicking on the primaru cell does *not* start an edit operation. - It simply selects the row. Pressing F2 edits the primary cell. - - CELLEDIT_DOUBLECLICK - Double clicking any cell starts an edit operation on that cell, including - the primary cell. Pressing F2 edits the primary cell. - - CELLEDIT_F2ONLY - Pressing F2 edits the primary cell. Tab/Shift-Tab can be used to edit other - cells. Clicking does not start any editing. - - * evenRowsBackColor - When `useAlternateBackColors` is true, even numbered rows will have this - background color. - - * handleStandardKeys - When this is True (the default), several standard keys will be handled as - commands by the ObjectListView. If this is False, they will be ignored. - - Ctrl-A - Select all model objects - - Ctrl-C - Put a text version of the selected rows onto the clipboard (on Windows, - this is will also put a HTML version into the clipboard) - - Left-Arrow, Right-Arrow - [GroupListView only] This will collapse/expand all selected groups. - - * oddRowsBackColor - When `useAlternateBackColors` is true, odd numbered rows will have this - background color. - - * rowFormatter - To further control the formatting of individual rows, this property - can be set to a callable that expects two parameters: the listitem whose - characteristics are to be set, and the model object being displayed on that row. - - The row formatter is called after the alternate back colours (if any) have been - set. - - Remember: the background and text colours are overridden by system defaults - while a row is selected. - - * typingSearchesSortColumn - If this boolean is True (the default), when the user types into the list, the - control will try to find a prefix match on the values in the sort column. If this - is False, or the list is unsorted or if the sorted column is marked as not - searchable (via `isSearchable` attribute), the primary column will be matched. - - * useAlternateBackColors - If this property is true, even and odd rows will be given different - background. The background colors are controlled by the properties - `evenRowsBackColor` and `oddRowsBackColor`. This is true by default. - """ - - CELLEDIT_NONE = 0 - CELLEDIT_SINGLECLICK = 1 - CELLEDIT_DOUBLECLICK = 2 - CELLEDIT_F2ONLY = 3 - - """Names of standard images used within the ObjectListView. If you want to use your - own image in place of a standard one, simple register it with AddNamedImages() using - one of the following names.""" - NAME_DOWN_IMAGE = "objectListView.downImage" - NAME_UP_IMAGE = "objectListView.upImage" - NAME_CHECKED_IMAGE = "objectListView.checkedImage" - NAME_UNCHECKED_IMAGE = "objectListView.uncheckedImage" - NAME_UNDETERMINED_IMAGE = "objectListView.undeterminedImage" - NAME_EXPANDED_IMAGE = "objectListView.expandedImage" - NAME_COLLAPSED_IMAGE = "objectListView.collapsedImage" - - """When typing into the list, a delay between keystrokes greater than this (in seconds) - will be interpretted as a new search and any previous search text will be cleared""" - SEARCH_KEYSTROKE_DELAY = 0.75 - - """When typing into a list and searching on an unsorted column, we don't even try to search - if there are more than this many rows.""" - MAX_ROWS_FOR_UNSORTED_SEARCH = 100000 - - def __init__(self, *args, **kwargs): - """ - Create an ObjectListView. - - Apart from the normal ListCtrl parameters, this constructor looks for any of the - following optional parameters: - - * `cellEditMode` - * `rowFormatter` - * `sortable` - * `useAlternateBackColors` - - The behaviour of these properties are described in the class documentation, except - for `sortable.` - - `sortable` controls whether the rows of the control will be sorted when the user - clicks on the header. This is true by default. If it is False, clicking the header - will be nothing, and no images will be registered in the image lists. This - parameter only has effect at creation time -- it has no impact after creation. - - """ - - # We have two collections of objects: our model objects and our working list - # ("innerList"). The model objects are those given to use by the user; the working - # list is what is actually used to populate the control. This separation let us - # modify what is presented to the user without losing our base data. This allows - # to (in the future) implement filtering or some other view-like capabilities. - # Currently, for ObjectListView, these collections will be identical, but for a - # GroupListView they are different. - self.modelObjects = [] - self.innerList = [] - self.columns = [] - self.sortColumnIndex = -1 - self.sortAscending = True - self.smallImageList = None - self.normalImageList = None - self.cellEditor = None - self.cellBeingEdited = None - self.selectionBeforeCellEdit = [] - self.checkStateColumn = None - self.handleStandardKeys = True - self.searchPrefix = u"" - self.whenLastTypingEvent = 0 - self.filter = None - self.objectToIndexMap = None - - self.rowFormatter = kwargs.pop("rowFormatter", None) - self.useAlternateBackColors = kwargs.pop("useAlternateBackColors", True) - self.sortable = kwargs.pop("sortable", True) - self.cellEditMode = kwargs.pop("cellEditMode", self.CELLEDIT_NONE) - self.typingSearchesSortColumn = kwargs.pop("typingSearchesSortColumn", True) - - self.evenRowsBackColor = wx.Colour(240, 248, 255) # ALICE BLUE - self.oddRowsBackColor = wx.Colour(255, 250, 205) # LEMON CHIFFON - - wx.ListCtrl.__init__(self, *args, **kwargs) - - if self.sortable: - self.EnableSorting() - - # NOTE: On Windows, ListCtrl's don't trigger EVT_LEFT_UP :( - - self.Bind(wx.EVT_CHAR, self._HandleChar) - self.Bind(wx.EVT_LEFT_DOWN, self._HandleLeftDown) - self.Bind(wx.EVT_LEFT_UP, self._HandleLeftClickOrDoubleClick) - self.Bind(wx.EVT_LEFT_DCLICK, self._HandleLeftClickOrDoubleClick) - self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self._HandleColumnBeginDrag) - self.Bind(wx.EVT_LIST_COL_END_DRAG, self._HandleColumnEndDrag) - self.Bind(wx.EVT_MOUSEWHEEL, self._HandleMouseWheel) - self.Bind(wx.EVT_SCROLLWIN, self._HandleScroll) - self.Bind(wx.EVT_SIZE, self._HandleSize) - - # When is this event triggered? - #self.Bind(wx.EVT_LIST_COL_DRAGGING, self._HandleColumnDragging) - - # For some reason under Linux, the default wx.StaticText always appears - # behind the ListCtrl. The GenStaticText class appears as it should. - if wx.Platform == "__WXGTK__": - from wx.lib.stattext import GenStaticText as StaticText - else: - StaticText = wx.StaticText - - self.stEmptyListMsg = StaticText(self, -1, "This list is empty", - wx.Point(0, 0), wx.Size(0, 0), wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE | wx.FULL_REPAINT_ON_RESIZE) - self.stEmptyListMsg.Hide() - self.stEmptyListMsg.SetForegroundColour(wx.LIGHT_GREY) - self.stEmptyListMsg.SetBackgroundColour(self.GetBackgroundColour()) - self.stEmptyListMsg.SetFont(wx.Font(24, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) - - - #--------------------------------------------------------------#000000#FFFFFF - # Setup - - def SetColumns(self, columns, repopulate=True): - """ - Set the list of columns that will be displayed. - - The elements of the list can be either ColumnDefn objects or a tuple holding the values - to be given to the ColumnDefn constructor. - - The first column is the primary column -- this will be shown in the the non-report views. - - This clears any preexisting CheckStateColumn. The first column that is a check state - column will be installed as the CheckStateColumn for this listview. - """ - sortCol = self.GetSortColumn() - wx.ListCtrl.ClearAll(self) - self.checkStateColumn = None - self.columns = [] - for x in columns: - if isinstance(x, ColumnDefn): - self.AddColumnDefn(x) - else: - self.AddColumnDefn(ColumnDefn(*x)) - # Try to preserve the column column - self.SetSortColumn(sortCol) - if repopulate: - self.RepopulateList() - - - def AddColumnDefn(self, defn): - """ - Append the given ColumnDefn object to our list of active columns. - - If this method is called directly, you must also call RepopulateList() - to populate the new column with data. - """ - self.columns.append(defn) - - info = wx.ListItem() - info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT - if isinstance(defn.headerImage, basestring) and self.smallImageList is not None: - info.m_image = self.smallImageList.GetImageIndex(defn.headerImage) - else: - info.m_image = defn.headerImage - if info.m_image != -1: - info.m_mask = info.m_mask | wx.LIST_MASK_IMAGE - info.m_format = defn.GetAlignment() - info.m_text = defn.title - info.m_width = defn.width - self.InsertColumnInfo(len(self.columns)-1, info) - - # Under Linux, the width doesn't take effect without this call - self.SetColumnWidth(len(self.columns)-1, defn.width) - - # The first checkbox column becomes the check state column for the control - if defn.HasCheckState() and self.checkStateColumn is None: - self.InstallCheckStateColumn(defn) - - - def _InitializeCheckBoxImages(self): - """ - Initialize some checkbox images for use by this control. - """ - def _makeBitmap(state, size): - bitmap = wx.EmptyBitmap(size, size) - dc = wx.MemoryDC(bitmap) - dc.Clear() - - # On Linux, the Renderer draws the checkbox too low - if wx.Platform == "__WXGTK__": - yOrigin = -1 - else: - yOrigin = 0 - wx.RendererNative.Get().DrawCheckBox(self, dc, (0, yOrigin, size, size), state) - dc.SelectObject(wx.NullBitmap) - return bitmap - - def _makeBitmaps(name, state): - self.AddNamedImages(name, _makeBitmap(state, 16), _makeBitmap(state, 32)) - - # If there isn't a small image list, make one - if self.smallImageList is None: - self.SetImageLists() - - _makeBitmaps(ObjectListView.NAME_CHECKED_IMAGE, wx.CONTROL_CHECKED) - _makeBitmaps(ObjectListView.NAME_UNCHECKED_IMAGE, wx.CONTROL_CURRENT) - _makeBitmaps(ObjectListView.NAME_UNDETERMINED_IMAGE, wx.CONTROL_UNDETERMINED) - - - def CreateCheckStateColumn(self, columnIndex=0): - """ - Create a fixed width column at the given index to show the checkedness - of objects in this list. - - If this is installed at column 0 (which is the default), the listview - should only be used in Report view. - - This should be called after SetColumns() has been called, since - SetColumns() removed any previous check state column. - - RepopulateList() or SetObjects() must be called after this. - """ - col = ColumnDefn("", fixedWidth=24, isEditable=False) - col.valueGetter = col.GetCheckState # Install a value getter so sorting works - col.stringConverter = lambda x: "" # We don't want any string for the value - col.isInternal = True # This is an autocreated column - self.columns.insert(columnIndex, col) - self.SetColumns(self.columns, False) - self.InstallCheckStateColumn(col) - - - def InstallCheckStateColumn(self, column): - """ - Configure the given column so that it shows the check state of each row in this - control. - - This column's checkbox will be toggled when the user pressed space when a row is - selected. - - `RepopulateList()` or `SetObjects()` must be called after a new check state column is - installed for the check state column to be visible. - - Set to None to remove the check state column. - """ - self.checkStateColumn = column - if column is None: - return - - if self.smallImageList == None or \ - not self.smallImageList.HasName(ObjectListView.NAME_CHECKED_IMAGE): - self._InitializeCheckBoxImages() - - # Is the column already configured to handle check state? - if column.HasCheckState(): - return - - # The column isn't managing it's own check state, so install handlers - # that will manage the state. This is useful when the checkedness is - # related to the view and is not an attribute of the model. - checkState = dict() - def _handleGetCheckState(modelObject): - return checkState.get(modelObject, False) # objects are not checked by default - - def _handleSetCheckState(modelObject, newValue): - checkState[modelObject] = newValue - return newValue - - column.checkStateGetter = _handleGetCheckState - column.checkStateSetter = _handleSetCheckState - - - def RegisterSortIndicators(self, sortUp=None, sortDown=None): - """ - Register the bitmaps that should be used to indicated which column is being sorted - These bitmaps must be the same dimensions as the small image list (not sure - why that should be so, but it is) - - If no parameters are given, 16x16 default images will be registered - """ - self.AddNamedImages(ObjectListView.NAME_DOWN_IMAGE, sortDown or _getSmallDownArrowBitmap()) - self.AddNamedImages(ObjectListView.NAME_UP_IMAGE, sortUp or _getSmallUpArrowBitmap()) - - - def SetImageLists(self, smallImageList=None, normalImageList=None): - """ - Remember the image lists to be used for this control. - - Call this without parameters to create reasonable default image lists. - - Use this to change the size of images shown by the list control. - """ - if isinstance(smallImageList, NamedImageList): - self.smallImageList = smallImageList - else: - self.smallImageList = NamedImageList(smallImageList, 16) - self.SetImageList(self.smallImageList.imageList, wx.IMAGE_LIST_SMALL) - - if isinstance(normalImageList, NamedImageList): - self.normalImageList = normalImageList - else: - self.normalImageList = NamedImageList(normalImageList, 32) - self.SetImageList(self.normalImageList.imageList, wx.IMAGE_LIST_NORMAL) - - - #--------------------------------------------------------------#000000#FFFFFF - # Commands - - def AddImages(self, smallImage=None, normalImage=None): - """ - Add the given images to the list of available images. Return the index of the image. - """ - return self.AddNamedImages(None, smallImage, normalImage) - - - def AddObject(self, modelObject): - """ - Add the given object to our collection of objects. - - The object will appear at its sorted location, or at the end of the list if - the list is unsorted - """ - self.AddObjects([modelObject]) - - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - - The objects will appear at their sorted locations, or at the end of the list if - the list is unsorted - """ - if len(self.innerList) == 0: - return self.SetObjects(modelObjects) - - try: - self.Freeze() - originalSize = len(self.innerList) - self.modelObjects.extend(modelObjects) - self._BuildInnerList() - item = wx.ListItem() - item.SetColumn(0) - for (i, x) in enumerate(self.innerList[originalSize:]): - item.Clear() - self._InsertUpdateItem(item, originalSize+i, x, True) - self._SortItemsNow() - finally: - self.Thaw() - - - def AddNamedImages(self, name, smallImage=None, normalImage=None): - """ - Add the given images to the list of available images. Return the index of the image. - - If a name is given, that name can later be used to refer to the images rather - than having to use the returned index. - """ - if isinstance(smallImage, basestring): - smallImage = wx.Bitmap(smallImage) - if isinstance(normalImage, basestring): - normalImage = wx.Bitmap(normalImage) - - # We must have image lists for images to be added to them - if self.smallImageList is None or self.normalImageList is None: - self.SetImageLists() - - # There must always be the same number of small and normal bitmaps, - # so if we aren't given one, we have to make an empty one of the right size - smallImage = smallImage or wx.EmptyBitmap(*self.smallImageList.GetSize(0)) - normalImage = normalImage or wx.EmptyBitmap(*self.normalImageList.GetSize(0)) - - self.smallImageList.AddNamedImage(name, smallImage) - return self.normalImageList.AddNamedImage(name, normalImage) - - - def AutoSizeColumns(self): - """ - Resize our auto sizing columns to match the data - """ - for (iCol, col) in enumerate(self.columns): - if col.width == wx.LIST_AUTOSIZE: - self.SetColumnWidth(iCol, wx.LIST_AUTOSIZE) - - # The new width must be within our minimum and maximum - colWidth = self.GetColumnWidth(iCol) - boundedWidth = col.CalcBoundedWidth(colWidth) - if colWidth != boundedWidth: - self.SetColumnWidth(iCol, boundedWidth) - - - def Check(self, modelObject): - """ - Mark the given model object as checked. - """ - self.SetCheckState(modelObject, True) - - - def ClearAll(self): - """ - Remove all items and columns - """ - wx.ListCtrl.ClearAll(self) - self.SetObjects(list()) - - - def CopyObjectsToClipboard(self, objects): - """ - Put a textual representation of the given objects onto the clipboard. - - This will be one line per object and tab-separated values per line. - Under windows there will be a HTML table version put on the clipboard as well. - """ - if objects is None or len(objects) == 0: - return - - # Get all the values of the given rows into multi-list - rows = self._GetValuesAsMultiList(objects) - - # Make a text version of the values - lines = [ "\t".join(x) for x in rows ] - txt = "\n".join(lines) + "\n" - - # Make a html version on Windows - try: - lines = [ "" + "".join(x) + "" for x in rows ] - html = "" + "".join(lines) + "
" - self._PutTextAndHtmlToClipboard(txt, html) - except ImportError: - cb = wx.Clipboard() - if cb.Open(): - cb.SetData(wx.TextDataObject(txt)) - cb.Flush() - cb.Close() - - def _GetValuesAsMultiList(self, objects): - """ - Return a list of lists of the string of the aspects of the given objects - """ - cols = self.columns[:] - if self.checkStateColumn is not None: - cols.remove(self.checkStateColumn) - return [[column.GetStringValue(x) for column in cols] for x in objects] - - - def _PutTextAndHtmlToClipboard(self, txt, fragment): - """ - Put the given text and html into the windows clipboard. - - The html will be written in accordance with strange "HTML Format" as specified - in http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/dataexchange/clipboard/htmlclipboardformat.asp - """ - import win32clipboard - - MARKER_BLOCK_OUTPUT = \ - "Version:1.0\r\n" \ - "StartHTML:%09d\r\n" \ - "EndHTML:%09d\r\n" \ - "StartFragment:%09d\r\n" \ - "EndFragment:%09d\r\n" \ - "StartSelection:%09d\r\n" \ - "EndSelection:%09d\r\n" \ - "SourceURL:%s\r\n" - - DEFAULT_HTML_BODY = \ - "" \ - "%s" - - html = DEFAULT_HTML_BODY % fragment - source = "http://objectlistview.sourceforge.net/python" - - fragmentStart = selectionStart = html.index(fragment) - fragmentEnd = selectionEnd = fragmentStart + len(fragment) - - # How long is the prefix going to be? - dummyPrefix = MARKER_BLOCK_OUTPUT % (0, 0, 0, 0, 0, 0, source) - lenPrefix = len(dummyPrefix) - - prefix = MARKER_BLOCK_OUTPUT % (lenPrefix, len(html)+lenPrefix, - fragmentStart+lenPrefix, fragmentEnd+lenPrefix, - selectionStart+lenPrefix, selectionEnd+lenPrefix, - source) - htmlForClipboard = (prefix + html) - - try: - win32clipboard.OpenClipboard(0) - win32clipboard.EmptyClipboard() - cfText = 1 - win32clipboard.SetClipboardData(cfText, txt) - cfHtml = win32clipboard.RegisterClipboardFormat("HTML Format") - win32clipboard.SetClipboardData(cfHtml, htmlForClipboard) - finally: - win32clipboard.CloseClipboard() - - - def CopySelectionToClipboard(self): - """ - Copy the selected objects to the clipboard - """ - self.CopyObjectsToClipboard(self.GetSelectedObjects()) - - - def DeleteAllItems(self): - """ - Remove all items - """ - wx.ListCtrl.DeleteAllItems(self) - self.SetObjects(list()) - - - def EnsureCellVisible(self, rowIndex, subItemIndex): - """ - Make sure the user can see all of the given cell, scrolling if necessary. - Return the bounds to the cell calculated after the cell has been made visible. - Return None if the cell cannot be made visible (non-Windows platforms can't scroll - the listview horizontally) - - If the cell is bigger than the ListView, the top left of the cell will be visible. - """ - self.EnsureVisible(rowIndex) - bounds = self.GetSubItemRect(rowIndex, subItemIndex, wx.LIST_RECT_BOUNDS) - boundsRight = bounds[0]+bounds[2] - if bounds[0] < 0 or boundsRight > self.GetSize()[0]: - if bounds[0] < 0: - horizDelta = bounds[0] - (self.GetSize()[0] / 4) - else: - horizDelta = boundsRight - self.GetSize()[0] + (self.GetSize()[0] / 4) - if wx.Platform == "__WXMSW__": - self.ScrollList(horizDelta, 0) - else: - return None - - return self.GetSubItemRect(rowIndex, subItemIndex, wx.LIST_RECT_LABEL) - - - def _FormatAllRows(self): - """ - Set up the required formatting on all rows - """ - for i in range(self.GetItemCount()): - item = self.GetItem(i) - self._FormatOneItem(item, i, self.GetObjectAt(i)) - self.SetItem(item) - - - def _FormatOneItem(self, item, index, model): - """ - Give the given row it's correct background color - """ - if self.useAlternateBackColors and self.InReportView(): - if index & 1: - item.SetBackgroundColour(self.oddRowsBackColor) - else: - item.SetBackgroundColour(self.evenRowsBackColor) - - if self.rowFormatter is not None: - self.rowFormatter(item, model) - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - self._SortObjects() - self._BuildInnerList() - self.Freeze() - try: - wx.ListCtrl.DeleteAllItems(self) - if len(self.innerList) == 0 or len(self.columns) == 0: - self.Refresh() - self.stEmptyListMsg.Show() - return - - self.stEmptyListMsg.Hide() - - # Insert all the rows - item = wx.ListItem() - item.SetColumn(0) - for (i, x) in enumerate(self.innerList): - item.Clear() - self._InsertUpdateItem(item, i, x, True) - - # Auto-resize once all the data has been added - self.AutoSizeColumns() - finally: - self.Thaw() - - - def RefreshIndex(self, index, modelObject): - """ - Refresh the item at the given index with data associated with the given object - """ - self._InsertUpdateItem(self.GetItem(index), index, modelObject, False) - - - def _InsertUpdateItem(self, listItem, index, modelObject, isInsert): - if isInsert: - listItem.SetId(index) - listItem.SetData(index) - - listItem.SetText(self.GetStringValueAt(modelObject, 0)) - listItem.SetImage(self.GetImageAt(modelObject, 0)) - self._FormatOneItem(listItem, index, modelObject) - - if isInsert: - self.InsertItem(listItem) - else: - self.SetItem(listItem) - - for iCol in range(1, len(self.columns)): - self.SetStringItem(index, iCol, self.GetStringValueAt(modelObject, iCol), - self.GetImageAt(modelObject, iCol)) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given model - """ - idx = self.GetIndexOf(modelObject) - if idx != -1: - self.RefreshIndex(self._MapModelIndexToListIndex(idx), modelObject) - - - def RefreshObjects(self, aList): - """ - Refresh all the objects in the given list - """ - try: - self.Freeze() - for x in aList: - self.RefreshObject(x) - finally: - self.Thaw() - - - def RemoveObject(self, modelObject): - """ - Remove the given object from our collection of objects. - """ - self.RemoveObjects([modelObject]) - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - """ - # Unlike AddObjects(), there is no clever way to do this -- we have to simply - # remove the objects and rebuild the whole list. We can't just remove the rows - # because every wxListItem holds the index of its matching model object. If we - # remove the first model object, the index of every object will change. - selection = self.GetSelectedObjects() - - # Use sets to quickly remove objects from self.modelObjects - # For large collections, this is MUCH faster. - try: - s1 = set(self.modelObjects) - s2 = set(modelObjects) - self.modelObjects = list(s1 - s2) - except TypeError: - # Not every object can be hashed, so some model objects cannot be placed in sets. - # For such objects, we have to resort to the slow method. - for x in modelObjects: - self.modelObjects.remove(x) - - self.RepopulateList() - self.SelectObjects(selection) - - - def _ResizeSpaceFillingColumns(self): - """ - Change the width of space filling columns so that they fill the - unoccupied width of the listview - """ - # If the list isn't in report view or there are no space filling columns, just return - if not self.InReportView(): - return - - # Don't do anything if there are no space filling columns - if True not in set(x.isSpaceFilling for x in self.columns): - return - - # Calculate how much free space is available in the control - totalFixedWidth = sum(self.GetColumnWidth(i) for (i, x) in enumerate(self.columns) - if not x.isSpaceFilling) - freeSpace = max(0, self.GetClientSizeTuple()[0] - totalFixedWidth) - - # Calculate the total number of slices the free space will be divided into - totalProportion = sum(x.freeSpaceProportion for x in self.columns if x.isSpaceFilling) - - # Space filling columns that would escape their boundary conditions - # are treated as fixed size columns - columnsToResize = [] - for (i, col) in enumerate(self.columns): - if col.isSpaceFilling: - newWidth = freeSpace * col.freeSpaceProportion / totalProportion - boundedWidth = col.CalcBoundedWidth(newWidth) - if newWidth == boundedWidth: - columnsToResize.append((i, col)) - else: - freeSpace -= boundedWidth - totalProportion -= col.freeSpaceProportion - if self.GetColumnWidth(i) != boundedWidth: - self.SetColumnWidth(i, boundedWidth) - - # Finally, give each remaining space filling column a proportion of the free space - for (i, col) in columnsToResize: - newWidth = freeSpace * col.freeSpaceProportion / totalProportion - boundedWidth = col.CalcBoundedWidth(newWidth) - if self.GetColumnWidth(i) != boundedWidth: - self.SetColumnWidth(i, boundedWidth) - - - def SetCheckState(self, modelObject, state): - """ - Set the check state of the given model object. - - 'state' can be True, False or None (which means undetermined) - """ - if self.checkStateColumn is None: - return None - else: - return self.checkStateColumn.SetCheckState(modelObject, state) - - - def SetColumnFixedWidth(self, colIndex, width): - """ - Make the given column to be fixed width - """ - if 0 <= colIndex < self.GetColumnCount(): - self.SetColumnWidth(colIndex, width) - self.columns[colIndex].SetFixedWidth(width) - - - def SetEmptyListMsg(self, msg): - """ - When there are no objects in the list, show this message in the control - """ - self.stEmptyListMsg.SetLabel(msg) - - - def SetEmptyListMsgFont(self, font): - """ - In what font should the empty list msg be rendered? - """ - self.stEmptyListMsg.SetFont(font) - - - def SetObjects(self, modelObjects, preserveSelection=False): - """ - Set the list of modelObjects to be displayed by the control. - """ - if preserveSelection: - selection = self.GetSelectedObjects() - - if modelObjects is None: - self.modelObjects = list() - else: - self.modelObjects = modelObjects[:] - - self.RepopulateList() - - if preserveSelection: - self.SelectObjects(selection) - - - # Synonym as per many wxWindows widgets - SetValue = SetObjects - - - def _BuildInnerList(self): - """ - Build the list that will actually populate the control - """ - # This is normally just the list of model objects - if self.filter: - self.innerList = self.filter(self.modelObjects) - else: - self.innerList = self.modelObjects - - # Our map isn't valid after doing this - self.objectToIndexMap = None - - - def ToggleCheck(self, modelObject): - """ - Toggle the "checkedness" of the given model. - - Checked becomes unchecked; unchecked or undetermined becomes checked. - """ - self.SetCheckState(modelObject, not self.IsChecked(modelObject)) - - - def Uncheck(self, modelObject): - """ - Mark the given model object as unchecked. - """ - self.SetCheckState(modelObject, False) - - #--------------------------------------------------------------#000000#FFFFFF - # Accessing - - def GetCheckedObjects(self): - """ - Return a collection of the modelObjects that are checked in this control. - """ - if self.checkStateColumn is None: - return list() - else: - return [x for x in self.innerList if self.IsChecked(x)] - - - def GetCheckState(self, modelObject): - """ - Return the check state of the given model object. - - Returns a boolean or None (which means undetermined) - """ - if self.checkStateColumn is None: - return None - else: - return self.checkStateColumn.GetCheckState(modelObject) - - - def GetFilter(self): - """ - Return the filter that is currently operating on this control. - """ - return self.filter - - - def GetFilteredObjects(self): - """ - Return the model objects that are actually displayed in the control. - - If no filter is in effect, this is the same as GetObjects(). - """ - return self.innerList - - - def GetFocusedRow(self): - """ - Return the index of the row that has the focus. -1 means no focus - """ - return self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_FOCUSED) - - - def GetIndexOf(self, modelObject): - """ - Return the index of the given modelObject in the list. - - This method works on the visible item in the control. If a filter - is in place, not all model object given to SetObjects() are visible. - """ - # Rebuild our index map if it has been invalidated. The TypeError - # exceptions are for objects that cannot be hashed (like lists) - if self.objectToIndexMap is None: - self.objectToIndexMap = dict() - for (i, x) in enumerate(self.innerList): - try: - self.objectToIndexMap[x] = i - except TypeError: - pass - - # Use our map to find the object (but fall back to simple search - # for non-hashable objects) - try: - return self.objectToIndexMap.get(modelObject, -1) - except TypeError: - try: - return self.innerList.index(modelObject) - except ValueError: - return -1 - - - def GetImageAt(self, modelObject, columnIndex): - """ - Return the index of the image that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - - # If the column is a checkbox column, return the image appropriate to the check - # state - if column.HasCheckState(): - name = { - True: ObjectListView.NAME_CHECKED_IMAGE, - False: ObjectListView.NAME_UNCHECKED_IMAGE, - None: ObjectListView.NAME_UNDETERMINED_IMAGE - }.get(column.GetCheckState(modelObject)) - return self.smallImageList.GetImageIndex(name) - - # Not a checkbox column, so just return the image - imageIndex = column.GetImage(modelObject) - if isinstance(imageIndex, basestring): - return self.smallImageList.GetImageIndex(imageIndex) - else: - return imageIndex - - - def GetObjectAt(self, index): - """ - Return the model object at the given row of the list. - """ - # Because of sorting, index can't be used directly, which is - # why we set the item data to be the real index - return self.innerList[self.GetItemData(index)] - - - def __getitem__(self, index): - """ - Synactic sugar over GetObjectAt() - """ - return self.GetObjectAt(index) - - - def GetObjects(self): - """ - Return the model objects that are available to the control. - - If no filter is in effect, this is the same as GetFilteredObjects(). - """ - return self.modelObjects - - - def GetPrimaryColumn(self): - """ - Return the primary column or None there is no primary column. - - The primary column is the first column given by the user. - This column is edited when F2 is pressed. - """ - i = self.GetPrimaryColumnIndex() - if i == -1: - return None - else: - return self.columns[i] - - - def GetPrimaryColumnIndex(self): - """ - Return the index of the primary column. Returns -1 when there is no primary column. - - The primary column is the first column given by the user. - This column is edited when F2 is pressed. - """ - for (i, x) in enumerate(self.columns): - if not x.isInternal: - return i - - return -1 - - - def GetSelectedObject(self): - """ - Return the selected modelObject or None if nothing is selected or if more than one is selected. - """ - model = None - for (i, x) in enumerate(self.YieldSelectedObjects()): - if i == 0: - model = x - else: - model = None - break - return model - - - def GetSelectedObjects(self): - """ - Return a list of the selected modelObjects - """ - return list(self.YieldSelectedObjects()) - - - def GetSortColumn(self): - """ - Return the column by which the rows of this control should be sorted - """ - if self.sortColumnIndex < 0 or self.sortColumnIndex >= len(self.columns): - return None - else: - return self.columns[self.sortColumnIndex] - - - def GetStringValueAt(self, modelObject, columnIndex): - """ - Return a string representation of the value that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - return column.GetStringValue(modelObject) - - - def GetValueAt(self, modelObject, columnIndex): - """ - Return the value that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - return column.GetValue(modelObject) - - - def IsCellEditing(self): - """ - Is some cell currently being edited? - """ - return self.cellEditor and self.cellEditor.IsShown() - - - def IsChecked(self, modelObject): - """ - Return a boolean indicating if the given modelObject is checked. - """ - return self.GetCheckState(modelObject) == True - - - def IsObjectSelected(self, modelObject): - """ - Is the given modelObject selected? - """ - return modelObject in self.GetSelectedObjects() - - - def SetFilter(self, filter): - """ - Remember the filter that is currently operating on this control. - Set this to None to clear the current filter. - - A filter is a callable that accepts one parameter: the original list - of model objects. The filter chooses which of these model objects should - be visible to the user, and returns a collection of only those objects. - - The Filter module has some useful standard filters. - - You must call RepopulateList() for changes to the filter to be visible. - """ - self.filter = filter - - - def SetSortColumn(self, column, resortNow=False): - """ - Set the column by which the rows should be sorted. - - 'column' can be None (which makes the list be unsorted), a ColumnDefn, - or the index of the column desired - """ - if column is None: - self.sortColumnIndex = -1 - elif isinstance(column, ColumnDefn): - try: - self.sortColumnIndex = self.columns.index(column) - except ValueError: - self.sortColumnIndex = -1 - else: - self.sortColumnIndex = column - if resortNow: - self.SortBy(self.sortColumnIndex) - else: - self._UpdateColumnSortIndicators() - - - def YieldSelectedObjects(self): - """ - Progressively yield the selected modelObjects - """ - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - yield self.GetObjectAt(i) - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - - - #---------------------------------------------------------------------------- - # Calculating - - def GetSubItemRect(self, rowIndex, subItemIndex, flag): - """ - Poor mans replacement for missing wxWindows method. - - The rect returned takes scroll position into account, so negative x and y are - possible. - """ - #print "GetSubItemRect(self, %d, %d, %d):" % (rowIndex, subItemIndex, flag) - - # Linux doesn't handle wx.LIST_RECT_LABEL flag. So we always get - # the whole bounds then par it down to the cell we want - rect = self.GetItemRect(rowIndex, wx.LIST_RECT_BOUNDS) - - if self.InReportView(): - rect = [0-self.GetScrollPos(wx.HORIZONTAL), rect.Y, 0, rect.Height] - for i in range(subItemIndex+1): - rect[0] += rect[2] - rect[2] = self.GetColumnWidth(i) - - # If we want only the label rect for sub items, we have to manually - # adjust for any image the subitem might have - if flag == wx.LIST_RECT_LABEL: - lvi = self.GetItem(rowIndex, subItemIndex) - if lvi.GetImage() != -1: - if self.HasFlag(wx.LC_ICON): - imageWidth = self.normalImageList.GetSize(0)[0] - rect[1] += imageWidth - rect[3] -= imageWidth - else: - imageWidth = self.smallImageList.GetSize(0)[0] + 1 - rect[0] += imageWidth - rect[2] -= imageWidth - - #print "rect=%s" % rect - return rect - - - def HitTestSubItem(self, pt): - """ - Return a tuple indicating which (item, subItem) the given pt (client coordinates) is over. - - This uses the buildin version on Windows, and poor mans replacement on other platforms. - """ - # The buildin version works on Windows - if wx.Platform == "__WXMSW__": - return wx.ListCtrl.HitTestSubItem(self, pt) - - (rowIndex, flags) = self.HitTest(pt) - - # Did the point hit any item? - if (flags & wx.LIST_HITTEST_ONITEM) == 0: - return (-1, 0, -1) - - # If it did hit an item and we are not in report mode, it must be the primary cell - if not self.InReportView(): - return (rowIndex, wx.LIST_HITTEST_ONITEM, 0) - - # Find which subitem is hit - right = 0 - scrolledX = self.GetScrollPos(wx.HORIZONTAL) + pt.x - for i in range(self.GetColumnCount()): - left = right - right += self.GetColumnWidth(i) - if scrolledX < right: - if (scrolledX - left) < self.smallImageList.GetSize(0)[0]: - flag = wx.LIST_HITTEST_ONITEMICON - else: - flag = wx.LIST_HITTEST_ONITEMLABEL - return (rowIndex, flag, i) - - return (rowIndex, 0, -1) - - - #---------------------------------------------------------------------------- - # Event handling - - def _HandleChar(self, evt): - if evt.GetKeyCode() == wx.WXK_F2 and not self.IsCellEditing(): - return self._PossibleStartCellEdit(self.GetFocusedRow(), self.GetPrimaryColumnIndex()) - - # We have to catch Return/Enter/Escape here since some types of controls - # (e.g. ComboBox, UserControl) don't trigger key events that we can listen for. - # Treat Return or Enter as committing the current edit operation unless the control - # is a multiline text control, in which case we treat it as data - if evt.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and self.IsCellEditing(): - if self.cellEditor and self.cellEditor.HasFlag(wx.TE_MULTILINE): - return evt.Skip() - else: - return self.FinishCellEdit() - - # Treat Escape as cancel the current edit operation - if evt.GetKeyCode() in (wx.WXK_ESCAPE, wx.WXK_CANCEL) and self.IsCellEditing(): - return self.CancelCellEdit() - - # Tab to the next editable column - if evt.GetKeyCode() == wx.WXK_TAB and self.IsCellEditing(): - return self._HandleTabKey(evt.ShiftDown()) - - # Space bar with a selection on a listview with checkboxes toggles the checkboxes - if (evt.GetKeyCode() == wx.WXK_SPACE and - not self.IsCellEditing() and - self.checkStateColumn is not None and - self.GetSelectedItemCount() > 0): - return self._ToggleCheckBoxForSelection() - - if not self.IsCellEditing(): - if self._HandleTypingEvent(evt): - return - - if not self.IsCellEditing() and self.handleStandardKeys: - # Copy selection on Ctrl-C - # Why is Ctrl-C represented by 3?! Is this Windows only? - if (evt.GetKeyCode() == 3): - self.CopySelectionToClipboard() - return - # Select All on Ctrl-A - if (evt.GetKeyCode() == 1): - self.SelectAll() - return - - evt.Skip() - - - def _HandleTypingEvent(self, evt): - """ - """ - if self.GetItemCount() == 0 or self.GetColumnCount() == 0: - return False - - if evt.GetModifiers() != 0 and evt.GetModifiers() != wx.MOD_SHIFT: - return False - - if evt.GetKeyCode() > wx.WXK_START: - return False - - if evt.GetKeyCode() in (wx.WXK_BACK, wx.WXK_DELETE): - self.searchPrefix = u"" - return True - - # On which column are we going to compare values? If we should search on the - # sorted column, and there is a sorted column and it is searchable, we use that - # one, otherwise we fallback to the primary column - if self.typingSearchesSortColumn and self.GetSortColumn() and self.GetSortColumn().isSearchable: - searchColumn = self.GetSortColumn() - else: - searchColumn = self.GetPrimaryColumn() - - # On Linux, GetUnicodeKey() always returns 0 -- on my 2.8.7.1 (gtk2-unicode) - if evt.GetUnicodeKey() == 0: - uniChar = chr(evt.GetKeyCode()) - else: - uniChar = unichr(evt.GetUnicodeKey()) - if uniChar not in string.printable: - return False - - # On Linux, evt.GetTimestamp() isn't reliable so use time.time() instead - timeNow = time.time() - if (timeNow - self.whenLastTypingEvent) > self.SEARCH_KEYSTROKE_DELAY: - self.searchPrefix = uniChar - else: - self.searchPrefix += uniChar - self.whenLastTypingEvent = timeNow - - #self.__rows = 0 - self._FindByTyping(searchColumn, self.searchPrefix) - #print "Considered %d rows in %2f secs" % (self.__rows, time.time() - timeNow) - - return True - - def _FindByTyping(self, searchColumn, prefix): - """ - Select the first row passed the currently focused row that has a string representation - that begins with 'prefix' in the given column - """ - start = max(self.GetFocusedRow(), 0) - - # If the user is starting a new search, we don't want to consider the current row - if len(prefix) == 1: - start = (start + 1) % self.GetItemCount() - - # If we are searching on a sorted column, use a binary search - if self._CanUseBisect(searchColumn): - if self._FindByBisect(searchColumn, prefix, start, self.GetItemCount()): - return - if self._FindByBisect(searchColumn, prefix, 0, start): - return - else: - # A binary search on a sorted column can handle any number of rows. A linear - # search cannot. So we impose an arbitrary limit on the number of rows to - # consider. Above that, we don't even try - if self.GetItemCount() > self.MAX_ROWS_FOR_UNSORTED_SEARCH: - self._SelectAndFocus(0) - return - - # Do a linear, wrapping search to find the next match. To wrap, we consider - # the rows in two partitions: start to the end of the collection, and then - # from the beginning to the start position. Expressing this in other languages - # is a pain, but it's elegant in Python. I just love Python :) - for i in itertools.chain(range(start, self.GetItemCount()), range(0, start)): - #self.__rows += 1 - model = self.GetObjectAt(i) - if model is not None: - strValue = searchColumn.GetStringValue(model) - if strValue.lower().startswith(prefix): - self._SelectAndFocus(i) - return - wx.Bell() - - def _CanUseBisect(self, searchColumn): - """ - Return True if we can use binary search on the given column - """ - # If the list isn't sorted or if it's sorted by some other column, we can't - if self.GetSortColumn() != searchColumn: - return False - - # If the column doesn't knows whether it should or not, make a guess based on the - # type of data in the column (strings and booleans are probably safe). We already - # know that the list isn't empty. - if searchColumn.useBinarySearch is None: - aspect = searchColumn.GetValue(self.GetObjectAt(0)) - searchColumn.useBinarySearch = isinstance(aspect, (basestring, bool)) - - return searchColumn.useBinarySearch - - def _FindByBisect(self, searchColumn, prefix, start, end): - """ - Use a binary search to look for rows that match the given prefix between the rows given. - - If a match was found, select/focus/reveal that row and return True. - """ - - # If the sorting is ascending, we use less than to find the first match - # If the sort is descending, we have to use greater-equal, and suffix the - # search string to make sure we find the first match (without the suffix - # we always find the last match) - if self.sortAscending: - cmpFunc = operator.lt - searchFor = prefix - else: - cmpFunc = operator.ge - searchFor = prefix + "z" - - # Adapted from bisect std module - lo = start - hi = end - while lo < hi: - mid = (lo + hi) // 2 - strValue = searchColumn.GetStringValue(self.GetObjectAt(mid)) - if cmpFunc(searchFor, strValue.lower()): - hi = mid - else: - lo = mid+1 - - if lo < start or lo >= end: - return False - - strValue = searchColumn.GetStringValue(self.GetObjectAt(lo)) - if strValue.lower().startswith(prefix): - self._SelectAndFocus(lo) - return True - - return False - - def _SelectAndFocus(self, rowIndex): - """ - Select and focus on the given row. - """ - self.DeselectAll() - self.Select(rowIndex) - self.Focus(rowIndex) - - def _ToggleCheckBoxForSelection(self): - """ - Toggles the checkedness of the selected modelObjects. - """ - selection = self.GetSelectedObjects() - newValue = not self.IsChecked(selection[0]) - for x in selection: - self.SetCheckState(x, newValue) - self.RefreshObjects(selection) - - def _HandleColumnBeginDrag(self, evt): - """ - Handle when the user begins to resize a column - """ - self._PossibleFinishCellEdit() - colIndex = evt.GetColumn() - if 0 > colIndex >= len(self.columns): - evt.Skip() - else: - col = self.columns[colIndex] - if col.IsFixedWidth() or col.isSpaceFilling: - evt.Veto() - else: - evt.Skip() - - - def _HandleColumnClick(self, evt): - """ - The user has clicked on a column title - """ - evt.Skip() - self._PossibleFinishCellEdit() - - # Toggle the sort column on the second click - if evt.GetColumn() == self.sortColumnIndex: - self.sortAscending = not self.sortAscending - else: - self.sortAscending = True - - self.SortBy(evt.GetColumn(), self.sortAscending) - self._FormatAllRows() - - - def _HandleColumnDragging(self, evt): - """ - A column is being dragged - """ - # When is this triggered? - - # The processing should be the same processing as Dragged - evt.Skip() - - - def _HandleColumnEndDrag(self, evt): - """ - The user has finished resizing a column. Make sure that it is not - bigger than it should be, then resize any space filling columns. - """ - colIndex = evt.GetColumn() - if 0 > colIndex >= len(self.columns): - evt.Skip() - else: - currentWidth = self.GetColumnWidth(colIndex) - col = self.columns[colIndex] - newWidth = col.CalcBoundedWidth(currentWidth) - if currentWidth != newWidth: - wx.CallAfter(self._SetColumnWidthAndResize, colIndex, newWidth) - else: - evt.Skip() - wx.CallAfter(self._ResizeSpaceFillingColumns) - - def _SetColumnWidthAndResize(self, colIndex, newWidth): - self.SetColumnWidth(colIndex, newWidth) - self._ResizeSpaceFillingColumns() - - - def _HandleLeftDown(self, evt): - """ - Handle a left down on the ListView - """ - evt.Skip() - - # Test for a mouse down on the image of the check box column - if self.InReportView(): - (row, flags, subitem) = self.HitTestSubItem(evt.GetPosition()) - else: - (row, flags) = self.HitTest(evt.GetPosition()) - subitem = 0 - - if flags == wx.LIST_HITTEST_ONITEMICON: - self._HandleLeftDownOnImage(row, subitem) - - - def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): - """ - Handle a left click on the image at the given cell - """ - column = self.columns[subItemIndex] - if not column.HasCheckState(): - return - - self._PossibleFinishCellEdit() - modelObject = self.GetObjectAt(rowIndex) - if modelObject is not None: - column.SetCheckState(modelObject, not column.GetCheckState(modelObject)) - self.RefreshIndex(rowIndex, modelObject) - - - def _HandleLeftClickOrDoubleClick(self, evt): - """ - Handle a left click or left double click on the ListView - """ - evt.Skip() - - # IF any modifiers are down, OR - # the listview isn't editable, OR - # we should edit on double click and this is a single click, OR - # we should edit on single click and this is a double click, - # THEN we don't try to start a cell edit operation - if evt.m_altDown or evt.m_controlDown or evt.m_shiftDown: - return - if self.cellEditMode == self.CELLEDIT_NONE: - return - if evt.LeftUp() and self.cellEditMode == self.CELLEDIT_DOUBLECLICK: - return - if evt.LeftDClick() and self.cellEditMode == self.CELLEDIT_SINGLECLICK: - return - - # Which item did the user click? - (rowIndex, flags, subItemIndex) = self.HitTestSubItem(evt.GetPosition()) - if (flags & wx.LIST_HITTEST_ONITEM) == 0 or subItemIndex == -1: - return - - # A single click on column 0 doesn't start an edit - if subItemIndex == 0 and self.cellEditMode == self.CELLEDIT_SINGLECLICK: - return - - self._PossibleStartCellEdit(rowIndex, subItemIndex) - - - def _HandleMouseWheel(self, evt): - """ - The user spun the mouse wheel - """ - self._PossibleFinishCellEdit() - evt.Skip() - - - def _HandleScroll(self, evt): - """ - The ListView is being scrolled - """ - self._PossibleFinishCellEdit() - evt.Skip() - - - def _HandleSize(self, evt): - """ - The ListView is being resized - """ - self._PossibleFinishCellEdit() - evt.Skip() - self._ResizeSpaceFillingColumns() - # Make sure our empty msg is reasonably positioned - sz = self.GetClientSize() - self.stEmptyListMsg.SetDimensions(0, sz.GetHeight()/3, sz.GetWidth(), sz.GetHeight()) - #self.stEmptyListMsg.Wrap(sz.GetWidth()) - - - def _HandleTabKey(self, isShiftDown): - """ - Handle a Tab key during a cell edit operation - """ - (rowBeingEdited, subItem) = self.cellBeingEdited - - # Prevent a nasty flicker when tabbing between fields where the selected rows - # are restored at the end of one cell edit, and removed at the start of the next - shadowSelection = self.selectionBeforeCellEdit - self.selectionBeforeCellEdit = [] - self.FinishCellEdit() - - # If we are in report view, move to the next (or previous) editable subitem, - # wrapping at the edges - if self.HasFlag(wx.LC_REPORT): - columnCount = self.GetColumnCount() - for ignored in range(columnCount-1): - if isShiftDown: - subItem = (columnCount + subItem - 1) % columnCount - else: - subItem = (subItem + 1) % columnCount - if self.columns[subItem].isEditable and self.GetColumnWidth(subItem) > 0: - self.StartCellEdit(rowBeingEdited, subItem) - break - - self.selectionBeforeCellEdit = shadowSelection - - - #--------------------------------------------------------------#000000#FFFFFF - # Sorting - - def EnableSorting(self): - """ - Enable automatic sorting when the user clicks on a column title - """ - self.Bind(wx.EVT_LIST_COL_CLICK, self._HandleColumnClick) - - # Install sort indicators if they don't already exist - if self.smallImageList is None: - self.SetImageLists() - if (not self.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and - self.smallImageList.GetSize(0) == (16,16)): - self.RegisterSortIndicators() - - - def SortBy(self, newColumnIndex, ascending=True): - """ - Sort the items by the given column - """ - oldSortColumnIndex = self.sortColumnIndex - self.sortColumnIndex = newColumnIndex - self.sortAscending = ascending - - # Let the world have a chance to sort the items - evt = OLVEvent.SortEvent(self, self.sortColumnIndex, self.sortAscending, self.IsVirtual()) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - return - - if not evt.wasHandled: - self._SortItemsNow() - - self._UpdateColumnSortIndicators(self.sortColumnIndex, oldSortColumnIndex) - - - def _SortItemsNow(self): - """ - Sort the actual items in the list now, according to the current column and order - """ - sortColumn = self.GetSortColumn() - if not sortColumn: - return - - secondarySortColumn = None # self.GetSecondarySortColumn() - - def _singleObjectComparer(col, object1, object2): - value1 = col.GetValue(object1) - value2 = col.GetValue(object2) - try: - return locale.strcoll(value1.lower(), value2.lower()) - except: - return cmp(value1, value2) - - def _objectComparer(object1, object2): - result = _singleObjectComparer(sortColumn, object1, object2) - if secondarySortColumn and result == 0: - result = _singleObjectComparer(secondarySortColumn, object1, object2) - return result - - self.SortListItemsBy(_objectComparer) - - - def SortListItemsBy(self, cmpFunc, ascending=None): - """ - Sort the existing list items using the given comparison function. - - The comparison function must accept two model objects as parameters. - - The primary users of this method are handlers of the SORT event that want - to sort the items by their own special function. - """ - if ascending is None: - ascending = self.sortAscending - - def _sorter(key1, key2): - cmpVal = cmpFunc(self.innerList[key1], self.innerList[key2]) - if ascending: - return cmpVal - else: - return -cmpVal - - self.SortItems(_sorter) - - - def _SortObjects(self, modelObjects=None, sortColumn=None, secondarySortColumn=None): - """ - Sort the given modelObjects in place. - - This does not change the information shown in the control itself. - """ - if modelObjects is None: - modelObjects = self.modelObjects - if sortColumn is None: - sortColumn = self.GetSortColumn() - if secondarySortColumn == sortColumn: - secondarySortColumn = None - - # If we don't have a sort column, we can't sort -- duhh - if sortColumn is None: - return - - # Let the world have a chance to sort the model objects - evt = OLVEvent.SortEvent(self, self.sortColumnIndex, self.sortAscending, True) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed() or evt.wasHandled: - return - - # When sorting large groups, this is called a lot. Make it efficent. - # It is more efficient (by about 30%) to try to call lower() and catch the - # exception than it is to test for the class - def _getSortValue(x): - primary = sortColumn.GetValue(x) - try: - primary = primary.lower() - except AttributeError: - pass - if secondarySortColumn: - secondary = secondarySortColumn.GetValue(x) - try: - secondary = secondary.lower() - except AttributeError: - pass - return (primary, secondary) - else: - return primary - - modelObjects.sort(key=_getSortValue, reverse=(not self.sortAscending)) - - # Sorting invalidates our object map - self.objectToIndexMap = None - - - def _UpdateColumnSortIndicators(self, sortColumnIndex=None, oldSortColumnIndex=-1): - """ - Change the column that is showing a sort indicator - """ - if sortColumnIndex is None: - sortColumnIndex = self.sortColumnIndex - # Remove the sort indicator from the old sort column - if oldSortColumnIndex >= 0: - headerImage = self.columns[oldSortColumnIndex].headerImage - if isinstance(headerImage, basestring) and self.smallImageList is not None: - headerImage = self.smallImageList.GetImageIndex(headerImage) - self.SetColumnImage(oldSortColumnIndex, headerImage) - - if sortColumnIndex >= 0 and self.smallImageList is not None: - if self.sortAscending: - imageIndex = self.smallImageList.GetImageIndex(ObjectListView.NAME_UP_IMAGE) - else: - imageIndex = self.smallImageList.GetImageIndex(ObjectListView.NAME_DOWN_IMAGE) - - if imageIndex != -1: - self.SetColumnImage(sortColumnIndex, imageIndex) - - - #--------------------------------------------------------------#000000#FFFFFF - # Selecting - - def SelectAll(self): - """ - Selected all rows in the control - """ - # -1 indicates 'all items' - self.SetItemState(-1, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - - def DeselectAll(self): - """ - De-selected all rows in the control - """ - # -1 indicates 'all items' - self.SetItemState(-1, 0, wx.LIST_STATE_SELECTED) - - - def SelectObject(self, modelObject, deselectOthers=True, ensureVisible=False): - """ - Select the given modelObject. If deselectOthers is True, all other rows will be deselected - """ - i = self.GetIndexOf(modelObject) - if i == -1: - return - - if deselectOthers: - self.DeselectAll() - - realIndex = self._MapModelIndexToListIndex(i) - self.SetItemState(realIndex, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - if ensureVisible: - self.EnsureVisible(realIndex) - - - def SelectObjects(self, modelObjects, deselectOthers=True): - """ - Select all of the given modelObjects. If deselectOthers is True, all other rows will be deselected - """ - if deselectOthers: - self.DeselectAll() - - for x in modelObjects: - self.SelectObject(x, False) - - - def _MapModelIndexToListIndex(self, modelIndex): - """ - Return the index in the list where the given model index lives - """ - return self.FindItemData(-1, modelIndex) - - #---------------------------------------------------------------------------- - # Cell editing - - def _PossibleStartCellEdit(self, rowIndex, subItemIndex): - """ - Start an edit operation on the given cell after performing some sanity checks - """ - if 0 > rowIndex >= self.GetItemCount(): - return - - if 0 > subItemIndex >= self.GetColumnCount(): - return - - if self.cellEditMode == self.CELLEDIT_NONE: - return - - if not self.columns[subItemIndex].isEditable: - return - - if self.GetObjectAt(rowIndex) is None: - return - - self.StartCellEdit(rowIndex, subItemIndex) - - - def _PossibleFinishCellEdit(self): - """ - If a cell is being edited, finish and commit an edit operation on the given cell. - """ - if self.IsCellEditing(): - self.FinishCellEdit() - - - def _PossibleCancelCellEdit(self): - """ - If a cell is being edited, cancel the edit operation. - """ - if self.IsCellEditing(): - self.CancelCellEdit() - - - def StartCellEdit(self, rowIndex, subItemIndex): - """ - Begin an edit operation on the given cell. - """ - - # Collect the information we need for the StartingEditEvent - modelObject = self.GetObjectAt(rowIndex) - cellValue = self.GetValueAt(modelObject, subItemIndex) - - # Make sure the user can see where the editor is going to be. If the bounds are - # null, this means we needed to scroll horizontally but were unable (this can only - # happen on non-Windows platforms). In that case we can't let the edit happen - # since the user won't be able to see the cell - cellBounds = self.EnsureCellVisible(rowIndex, subItemIndex) - if cellBounds is None: - wx.Bell() - return - - # Give the world the chance to veto the edit, or to change its characteristics - defaultEditor = self._MakeDefaultCellEditor(rowIndex, subItemIndex, cellValue) - evt = OLVEvent.CellEditStartingEvent(self, rowIndex, subItemIndex, modelObject, - cellValue, cellBounds, defaultEditor) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - defaultEditor.Destroy() - return - - # Remember that we are editing something (and make sure we can see it) - self.selectionBeforeCellEdit = self.GetSelectedObjects() - self.DeselectAll() - self.cellEditor = evt.newEditor or evt.editor - self.cellBeingEdited = (rowIndex, subItemIndex) - - # If we aren't using the default editor, destroy it - if self.cellEditor != defaultEditor: - defaultEditor.Destroy() - - # If the event handler hasn't already configured the editor, do it now. - if evt.shouldConfigureEditor: - self.cellEditor.SetFocus() - self.cellEditor.SetValue(evt.cellValue) - self._ConfigureCellEditor(self.cellEditor, evt.cellBounds, rowIndex, subItemIndex) - - # Let the world know the cell editing has started - evt = OLVEvent.CellEditStartedEvent(self, rowIndex, subItemIndex, modelObject, - cellValue, cellBounds, defaultEditor) - self.GetEventHandler().ProcessEvent(evt) - - self.cellEditor.Show() - self.cellEditor.Raise() - - - def _ConfigureCellEditor(self, editor, bounds, rowIndex, subItemIndex): - """ - Perform the normal configuration on the cell editor. - """ - editor.SetDimensions(*bounds) - - colour = self.GetItemBackgroundColour(rowIndex) - if colour.IsOk(): - editor.SetBackgroundColour(colour) - else: - editor.SetBackgroundColour(self.GetBackgroundColour()) - - colour = self.GetItemTextColour(rowIndex) - if colour.IsOk(): - editor.SetForegroundColour(colour) - else: - editor.SetForegroundColour(self.GetTextColour()) - - font = self.GetItemFont(rowIndex) - if font.IsOk(): - editor.SetFont(font) - else: - editor.SetFont(self.GetFont()) - - if hasattr(self.cellEditor, "SelectAll"): - self.cellEditor.SelectAll() - - editor.Bind(wx.EVT_CHAR, self._Editor_OnChar) - editor.Bind(wx.EVT_COMMAND_ENTER, self._Editor_OnChar) - editor.Bind(wx.EVT_KILL_FOCUS, self._Editor_KillFocus) - - - def _MakeDefaultCellEditor(self, rowIndex, subItemIndex, value): - """ - Return an editor that can edit the value of the given cell. - """ - - # The column could have editor creation function registered. - # Otherwise, we have to guess the editor from the type of the value. - # If the given cell actually holds None, we can't decide what editor to use. - # So we try to find any non-null value in the same column. - # If all else fails, we use a string editor. - creatorFunction = self.columns[subItemIndex].cellEditorCreator - if creatorFunction is None: - value = value or self._CalcNonNullValue(subItemIndex) - creatorFunction = CellEditor.CellEditorRegistry().GetCreatorFunction(value) - if creatorFunction is None: - creatorFunction = CellEditor.CellEditorRegistry().GetCreatorFunction("") - return creatorFunction(self, rowIndex, subItemIndex) - - - def _CalcNonNullValue(self, colIndex, maxRows=1000): - """ - Return the first non-null value in the given column, processing - at most maxRows rows - """ - column = self.columns[colIndex] - for i in range(min(self.GetItemCount(), maxRows)): - model = self.GetObjectAt(i) - if model is not None: - value = column.GetValue(model) - if value is not None: - return value - return None - - - def _Editor_OnChar(self, evt): - """ - A character has been pressed in a cell editor - """ - self._HandleChar(evt) - - - def _Editor_KillFocus(self, evt): - evt.Skip() - - # Some control trigger FocusLost events even when they still have focus - focusWindow = wx.Window.FindFocus() - #if focusWindow is not None and self.cellEditor != focusWindow: - if self.cellEditor != focusWindow: - self._PossibleFinishCellEdit() - - - def FinishCellEdit(self): - """ - Finish and commit an edit operation on the given cell. - """ - (rowIndex, subItemIndex) = self.cellBeingEdited - - # Give the world the chance to veto the edit, or to change its characteristics - rowModel = self.GetObjectAt(rowIndex) - evt = OLVEvent.CellEditFinishingEvent(self, rowIndex, subItemIndex, rowModel, - self.cellEditor.GetValue(), self.cellEditor, False) - self.GetEventHandler().ProcessEvent(evt) - if not evt.IsVetoed() and evt.cellValue is not None: - self.columns[subItemIndex].SetValue(rowModel, evt.cellValue) - self.RefreshIndex(rowIndex, rowModel) - - evt = OLVEvent.CellEditFinishedEvent(self, rowIndex, subItemIndex, rowModel, False) - self.GetEventHandler().ProcessEvent(evt) - - self._CleanupCellEdit() - - - def CancelCellEdit(self): - """ - Cancel an edit operation on the given cell. - """ - # Tell the world that the user cancelled the edit - (rowIndex, subItemIndex) = self.cellBeingEdited - evt = OLVEvent.CellEditFinishingEvent(self, rowIndex, subItemIndex, - self.GetObjectAt(rowIndex), - self.cellEditor.GetValue(), - self.cellEditor, - True) - self.GetEventHandler().ProcessEvent(evt) - - evt = OLVEvent.CellEditFinishedEvent(self, rowIndex, subItemIndex, rowModel, True) - self.GetEventHandler().ProcessEvent(evt) - - self._CleanupCellEdit() - - - def _CleanupCellEdit(self): - """ - Cleanup after finishing a cell edit operation - """ - self.SelectObjects(self.selectionBeforeCellEdit) - if self.cellEditor: - self.cellEditor.Hide() - self.cellEditor = None - self.cellBeingEdited = None - self.SetFocus() - - -######################################################################## - -class AbstractVirtualObjectListView(ObjectListView): - """ - This class holds the behaviour that is common to all virtual lists. - - A virtual list must be given an "object getter", which is a callable that accepts the - index of the model object required and returns the model. This can be set via the - SetObjectGetter() method, or passed into the constructor as the "getter" parameter. - - Due to the vagarities of virtual lists, rowFormatters must operate in a slightly - different manner for virtual lists. Instead of being passed a ListItem, rowFormatters - are passed a ListItemAttr instance. This supports the same formatting methods as a - ListItem -- SetBackgroundColour(), SetTextColour(), SetFont() -- but no other ListItem - methods. Obviously, being a virtual list, the rowFormatter cannot call any SetItem* - method on the ListView itself. - - """ - - def __init__(self, *args, **kwargs): - self.lastGetObjectIndex = -1 - self.lastGetObject = None - self.objectGetter = None - self.listItemAttr = None - #self.cacheHit = 0 - #self.cacheMiss = 0 - - self.SetObjectGetter(kwargs.pop("getter", None)) - - # We have to set the item count after the list has been created - if "count" in kwargs: - wx.CallAfter(self.SetItemCount, kwargs.pop("count")) - - # Virtual lists have to be in report format - kwargs["style"] = kwargs.get("style", 0) | wx.LC_REPORT | wx.LC_VIRTUAL - - ObjectListView.__init__(self, *args, **kwargs) - - - #---------------------------------------------------------------------------- - # Commands - - def ClearAll(self): - """ - Remove all items and columns - """ - ObjectListView.ClearAll(self) - self.lastGetObjectIndex = -1 - # Should this call SetItemCount()? - - - def DeleteAllItems(self): - """ - Remove all items - """ - ObjectListView.DeleteAllItems(self) - self.lastGetObjectIndex = -1 - # Should this call SetItemCount()? - - - def RefreshIndex(self, index, modelObject): - """ - Refresh the item at the given index with data associated with the given modelObject - """ - self.lastGetObjectIndex = -1 - self.RefreshItem(index) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given modelObject - """ - # We only have a hammer so everything looks like a nail - self.RefreshObjects() - - - def RefreshObjects(self, aList=None): - """ - Refresh all the objects in the given list - """ - # We can only refresh everything - self.lastGetObjectIndex = -1 - if self.GetItemCount() > 0:self.RefreshItems(0, self.GetItemCount()-1) - #self.Refresh() - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - # Virtual lists never need to rebuild -- they simply redraw - self.RefreshObjects() - - - def SetItemCount(self, count): - """ - Change the number of items visible in the list - """ - wx.ListCtrl.SetItemCount(self, count) - self.stEmptyListMsg.Show(count == 0) - self.lastGetObjectIndex = -1 - - - def SetObjectGetter(self, aCallable): - """ - Remember the callback that will be used to fetch the objects being displayed in - this list - """ - self.objectGetter = aCallable - - - def _FormatAllRows(self): - """ - Set up the required formatting on all rows - """ - # This is handled within OnGetItemAttr() - pass - - #---------------------------------------------------------------------------- - # Virtual list callbacks. - # These are called a lot! Keep them efficient - - def OnGetItemText(self, itemIdx, colIdx): - """ - Return the text that should be shown at the given cell - """ - return self.GetStringValueAt(self.GetObjectAt(itemIdx), colIdx) - - - def OnGetItemImage(self, itemIdx): - """ - Return the image index that should be shown on the primary column of the given item - """ - return self.GetImageAt(self.GetObjectAt(itemIdx), 0) - - - def OnGetItemColumnImage(self, itemIdx, colIdx): - """ - Return the image index at should be shown at the given cell - """ - return self.GetImageAt(self.GetObjectAt(itemIdx), colIdx) - - - def OnGetItemAttr(self, itemIdx): - """ - Return the display attributes that should be used for the given row - """ - if not self.useAlternateBackColors and self.rowFormatter is None: - return None - - # We have to keep a reference to the ListItemAttr or the garbage collector - # will clear it up immeditately, before the ListCtrl has time to process it. - self.listItemAttr = wx.ListItemAttr() - self._FormatOneItem(self.listItemAttr, itemIdx, self.GetObjectAt(itemIdx)) - - return self.listItemAttr - - - #---------------------------------------------------------------------------- - # Accessing - - def GetObjectAt(self, index): - """ - Return the model modelObject at the given row of the list. - - This method is called a lot! Keep it as efficient as possible. - """ - - # For reasons of performance, it may even be worthwhile removing this test and - # ensure/assume that objectGetter is never None - if self.objectGetter is None: - return None - - #if index == self.lastGetObjectIndex: - # self.cacheHit += 1 - #else: - # self.cacheMiss += 1 - #print "hit: %d / miss: %d" % (self.cacheHit, self.cacheMiss) - - # Cache the last result (the hit rate is normally good: 5-10 hits to 1 miss) - if index != self.lastGetObjectIndex: - self.lastGetObjectIndex = index - self.lastGetObject = self.objectGetter(index) - - return self.lastGetObject - - - -######################################################################## - -class VirtualObjectListView(AbstractVirtualObjectListView): - """ - A virtual object list displays various aspects of an unlimited numbers of objects in a - multi-column list control. - - By default, a VirtualObjectListView cannot sort its rows when the user click on a header. - If you have a back store that can sort the data represented in the virtual list, you - can listen for the EVT_SORT events, and then order your model objects accordingly. - - Due to the vagarities of virtual lists, rowFormatters must operate in a slightly - different manner for virtual lists. Instead of being passed a ListItem, rowFormatters - are passed a ListItemAttr instance. This supports the same formatting methods as a - ListItem -- SetBackgroundColour(), SetTextColour(), SetFont() -- but no other ListItem - methods. Obviously, being a virtual list, the rowFormatter cannot call any SetItem* - method on the ListView itself. - - """ - - def __init__(self, *args, **kwargs): - - # By default, virtual lists aren't sortable - if "sortable" not in kwargs: - kwargs["sortable"] = False - - AbstractVirtualObjectListView.__init__(self, *args, **kwargs) - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - - This cannot work for virtual lists since the source of model objects is not - under the control of the VirtualObjectListView. - """ - pass - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - - This cannot work for virtual lists since the source of model objects is not - under the control of the VirtualObjectListView. - """ - pass - - - def SelectObject(self, modelObject, deselectOthers=True): - """ - Select the given modelObject. If deselectOthers is True, all other objects will be deselected - - This doesn't work for virtual lists, since the virtual list has no way - of knowing where 'modelObject' is within the list. - """ - pass - - - def SelectObjects(self, modelObjects, deselectOthers=True): - """ - Select all of the given modelObjects. If deselectOthers is True, all other modelObjects will be deselected - - This doesn't work for virtual lists, since the virtual list has no way - of knowing where any of the modelObjects are within the list. - """ - pass - - - #---------------------------------------------------------------------------- - # Sorting - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - VirtualObjectListView can't sort anything by themselves, so this is a no-op. - """ - pass - -######################################################################## - -class FastObjectListView(AbstractVirtualObjectListView): - """ - A fast object list view is a nice compromise between the functionality of an ObjectListView - and the speed of a VirtualObjectListView. - - This class codes around the limitations of a virtual list. Specifically, it allows - sorting and selection by object. - """ - - def __init__(self, *args, **kwargs): - - AbstractVirtualObjectListView.__init__(self, *args, **kwargs) - - self.SetObjectGetter(lambda index: self.innerList[index]) - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - """ - self.modelObjects.extend(modelObjects) - # We don't want to call RepopulateList() here since that makes the whole - # control redraw, which flickers slightly, which I *really* hate! So we - # most of the work of RepopulateList() but only redraw from the first - # added object down. - self._SortObjects() - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - - # Find where the first added object appears and make that and everything - # after it redraw - first = self.GetItemCount() - for x in modelObjects: - # Because of filtering the added objects may not be in the list - idx = self.GetIndexOf(x) - if idx != -1: - first = min(first, idx) - if first == 0: - break - - if first < self.GetItemCount(): - self.RefreshItems(first, self.GetItemCount() - 1) - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - self.lastGetObjectIndex = -1 - self._SortObjects() - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - self.RefreshObjects() - - # Auto-resize once all the data has been added - self.AutoSizeColumns() - - - def RefreshObjects(self, aList=None): - """ - Refresh all the objects in the given list - """ - self.lastGetObjectIndex = -1 - # If no list is given, refresh everything - if aList: - for x in aList: - idx = self.GetIndexOf(x) - if idx != -1: - self.RefreshItem(idx) - else: - if self.GetItemCount() > 0:self.RefreshItems(0, self.GetItemCount() - 1) - - #---------------------------------------------------------------------------- - # Accessing - - def _MapModelIndexToListIndex(self, modelIndex): - """ - Return the index in the list where the given model index lives - """ - - # In a FastListView, the model index is the same as the list index - return modelIndex - - #---------------------------------------------------------------------------- - # Sorting - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - FastObjectListView don't sort the items, they sort the model objects themselves. - """ - selection = self.GetSelectedObjects() - self._SortObjects() - - self.SelectObjects(selection) - self.RefreshObjects() - - -####################################################################### - -class GroupListView(FastObjectListView): - """ - An ObjectListView that allows model objects to be organised into collapsable groups. - - GroupListView only work in report view. - - The appearance of the group headers are controlled by the 'groupFont', 'groupTextColour', - and 'groupBackgroundColour' public variables. - - The images used for expanded and collapsed groups can be controlled by changing - the images name 'ObjectListView.NAME_EXPANDED_IMAGE' and 'ObjectListView.NAME_COLLAPSED_IMAGE' - respectfully. Like this:: - - self.AddNamedImages(ObjectListView.NAME_EXPANDED_IMAGE, myOtherImage1) - self.AddNamedImages(ObjectListView.NAME_COLLAPSED_IMAGE, myOtherImage2) - - Public variables: - - * putBlankLineBetweenGroups - When this is True (the default), the list will be built so there is a blank - line between groups. - - """ - - #---------------------------------------------------------------------------- - # Creation - - def __init__(self, *args, **kwargs): - """ - Create a GroupListView. - - Parameters: - - * showItemCounts - - If this is True (the default) Group title will include the count of the items - that are within that group. - - * useExpansionColumn - - If this is True (the default), the expansion/contraction icon will have its - own column at position 0. If this is false, the expand/contract icon will be - in the first user specified column. This must be set before SetColumns() is called. - If it is changed, SetColumns() must be called again. - """ - self.groups = list() - self.showGroups = True - self.putBlankLineBetweenGroups = True - self.alwaysGroupByColumnIndex = -1 - self.useExpansionColumn = kwargs.pop("useExpansionColumn", True) - self.showItemCounts = kwargs.pop("showItemCounts", True) - FastObjectListView.__init__(self, *args, **kwargs) - - # Setup default group characteristics - font = self.GetFont() - self.groupFont = wx.FFont(font.GetPointSize(), font.GetFamily(), wx.FONTFLAG_BOLD, font.GetFaceName()) - self.groupTextColour = wx.Colour(33, 33, 33, 255) - self.groupBackgroundColour = wx.Colour(159, 185, 250, 249) - - self._InitializeImages() - - - def _InitializeImages(self): - """ - Initialize the images used to indicate expanded/collapsed state of groups. - """ - def _makeBitmap(state, size): - bitmap = wx.EmptyBitmap(size, size) - dc = wx.MemoryDC(bitmap) - dc.SetBackground(wx.Brush(self.groupBackgroundColour)) - dc.Clear() - (x, y) = (0, 0) - # The image under Linux is smaller and needs to be offset somewhat to look reasonable - if wx.Platform == "__WXGTK__": - (x, y) = (4, 4) - wx.RendererNative.Get().DrawTreeItemButton(self, dc, (x, y, size, size), state) - dc.SelectObject(wx.NullBitmap) - return bitmap - - # If there isn't a small image list, make one - if self.smallImageList is None: - self.SetImageLists() - - size = self.smallImageList.GetSize()[0] - self.AddNamedImages(ObjectListView.NAME_EXPANDED_IMAGE, _makeBitmap(wx.CONTROL_EXPANDED, size)) - self.AddNamedImages(ObjectListView.NAME_COLLAPSED_IMAGE, _makeBitmap(0, size)) - - - #---------------------------------------------------------------------------- - # Accessing - - def GetShowGroups(self): - """ - Return whether or not this control is showing groups of objects or a straight list - """ - return self.showGroups - - - def SetShowGroups(self, showGroups=True): - """ - Set whether or not this control is showing groups of objects or a straight list - """ - if showGroups == self.showGroups: - return - - self.showGroups = showGroups - if not len(self.columns): - return - - if showGroups: - self.SetColumns(self.columns, False) - else: - if self.useExpansionColumn: - self.SetColumns(self.columns[1:], False) - - self.SetObjects(self.modelObjects) - - - def GetShowItemCounts(self): - """ - Return whether or not the number of items in a groups should be included in the title - """ - return self.showItemCounts - - - def SetShowItemCounts(self, showItemCounts=True): - """ - Set whether or not the number of items in a groups should be included in the title - """ - if showItemCounts != self.showItemCounts: - self.showItemCounts = showItemCounts - self._BuildGroupTitles(self.groups, self.GetGroupByColumn()) - self._SetGroups(self.groups) - - - def GetGroupByColumn(self): - """ - Return the column by which the rows should be grouped - """ - if self.alwaysGroupByColumnIndex >= 0: - return self.GetAlwaysGroupByColumn() - elif self.GetSortColumn() is None: - return self.GetPrimaryColumn() - else: - return self.GetSortColumn() - - - def GetAlwaysGroupByColumn(self): - """ - Get the column by which the rows should be always be grouped. - """ - try: - return self.columns[self.alwaysGroupByColumnIndex] - except IndexError: - return None - - - def SetAlwaysGroupByColumn(self, column): - """ - Set the column by which the rows should be always be grouped. - - 'column' can be None (which clears the setting), a ColumnDefn, - or the index of the column desired - """ - if column is None: - self.alwaysGroupByColumnIndex = -1 - elif isinstance(column, ColumnDefn): - try: - self.alwaysGroupByColumnIndex = self.columns.index(column) - except ValueError: - self.alwaysGroupByColumnIndex = -1 - else: - self.alwaysGroupByColumnIndex = column - - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - """ - self.groups = None - FastObjectListView.AddObjects(self, modelObjects) - - - def CreateCheckStateColumn(self, columnIndex=0): - """ - Create a fixed width column at the given index to show the checkedness - of objects in this list. - """ - # If the control is configured to have a separate expansion column, - # the check state column has to come after that - if self.useExpansionColumn and columnIndex == 0: - columnIndex = 1 - FastObjectListView.CreateCheckStateColumn(self, columnIndex) - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - """ - self.groups = None - FastObjectListView.RemoveObjects(self, modelObjects) - - - def SetColumns(self, columns, repopulate=True): - """ - Set the columns for this control. - """ - newColumns = columns[:] - # Insert the column used for expansion and contraction (if one isn't already there) - if self.showGroups and self.useExpansionColumn and len(newColumns) > 0: - if not isinstance(newColumns[0], ColumnDefn) or not newColumns[0].isInternal: - newColumns.insert(0, ColumnDefn("", fixedWidth=24, isEditable=False)) - newColumns[0].isInternal = True - FastObjectListView.SetColumns(self, newColumns, repopulate) - - - def SetGroups(self, groups): - """ - Present the collection of ListGroups in this control. - - Calling this automatically put the control into ShowGroup mode - """ - self.modelObjects = list() - self.SetShowGroups(True) - self._SetGroups(groups) - - - def SetObjects(self, modelObjects, preserveSelection=False): - """ - Set the list of modelObjects to be displayed by the control. - """ - # Force our groups to be rebuilt, if we are supposd to be showing them - if self.showGroups: - self.groups = None - else: - self.groups = list() - FastObjectListView.SetObjects(self, modelObjects, preserveSelection) - - - #---------------------------------------------------------------------------- - # Building - - def _SetGroups(self, groups): - """ - Present the collection of ListGroups in this control. - """ - self.groups = groups - self.RepopulateList() - - - def RebuildGroups(self): - """ - Completely rebuild our groups from our current list of model objects. - - Only use this if SetObjects() has been called. If you have specifically created - your groups and called SetGroups(), do not use this method. - """ - groups = self._BuildGroups() - self.SortGroups(groups) - self._SetGroups(groups) - - - def _BuildGroups(self, modelObjects=None): - """ - Partition the given list of objects into ListGroups depending on the given groupBy column. - - Returns the created collection of ListGroups - """ - if modelObjects is None: - modelObjects = self.modelObjects - if self.filter: - modelObjects = self.filter(modelObjects) - - groupingColumn = self.GetGroupByColumn() - - groupMap = {} - for model in modelObjects: - key = groupingColumn.GetGroupKey(model) - group = groupMap.get(key) - if group is None: - groupMap[key] = group = ListGroup(key, groupingColumn.GetGroupKeyAsString(key)) - group.Add(model) - - groups = groupMap.values() - - if self.GetShowItemCounts(): - self._BuildGroupTitles(groups, groupingColumn) - - # Let the world know that we are creating the given groups - evt = OLVEvent.GroupCreationEvent(self, groups) - self.GetEventHandler().ProcessEvent(evt) - - return evt.groups - - - def _BuildGroupTitles(self, groups, groupingColumn): - """ - Rebuild the titles of the given groups - """ - for x in groups: - x.title = groupingColumn.GetGroupTitle(x, self.GetShowItemCounts()) - - - def _BuildInnerList(self): - """ - Build the list that will be used to populate the ListCtrl. - - This internal list is an amalgum of model objects, ListGroups - and None (which are blank rows). - """ - self.objectToIndexMap = None - if not self.showGroups: - return ObjectListView._BuildInnerList(self) - - if not self.modelObjects: - self.groups = list() - self.innerList = list() - return - - if self.groups is None: - self.groups = self._BuildGroups() - self.SortGroups() - - self.innerList = list() - for grp in self.groups: - if len(self.innerList) and self.putBlankLineBetweenGroups: - self.innerList.append(None) - self.innerList.append(grp) - if grp.isExpanded: - self.innerList.extend(grp.modelObjects) - - #---------------------------------------------------------------------------- - # Virtual list callbacks. - # These are called a lot! Keep them efficient - - def OnGetItemText(self, itemIdx, colIdx): - """ - Return the text that should be shown at the given cell - """ - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return "" - - if isinstance(modelObject, ListGroup): - if self.GetPrimaryColumnIndex() == colIdx: - return modelObject.title - else: - return "" - - return self.GetStringValueAt(modelObject, colIdx) - - - def OnGetItemImage(self, itemIdx): - """ - Return the image index that should be shown on the primary column of the given item - """ - # I don't think this method is ever called. Maybe in non-details views. - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return -1 - - if isinstance(modelObject, ListGroup): - if modelObject.isExpanded: - imageKey = ObjectListView.NAME_EXPANDED_IMAGE - else: - imageKey = ObjectListView.NAME_COLLAPSED_IMAGE - return self.smallImageList.GetImageIndex(imageKey) - - return self.GetImageAt(modelObject, 0) - - - def OnGetItemColumnImage(self, itemIdx, colIdx): - """ - Return the image index at should be shown at the given cell - """ - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return -1 - - if isinstance(modelObject, ListGroup): - if colIdx == 0: - if modelObject.isExpanded: - imageKey = ObjectListView.NAME_EXPANDED_IMAGE - else: - imageKey = ObjectListView.NAME_COLLAPSED_IMAGE - return self.smallImageList.GetImageIndex(imageKey) - else: - return -1 - - return self.GetImageAt(modelObject, colIdx) - - - def OnGetItemAttr(self, itemIdx): - """ - Return the display attributes that should be used for the given row - """ - self.listItemAttr = wx.ListItemAttr() - - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return self.listItemAttr - - if isinstance(modelObject, ListGroup): - # We have to keep a reference to the ListItemAttr or the garbage collector - # will clear it up immeditately, before the ListCtrl has time to process it. - if self.groupFont is not None: - self.listItemAttr.SetFont(self.groupFont) - if self.groupTextColour is not None: - self.listItemAttr.SetTextColour(self.groupTextColour) - if self.groupBackgroundColour is not None: - self.listItemAttr.SetBackgroundColour(self.groupBackgroundColour) - return self.listItemAttr - - return FastObjectListView.OnGetItemAttr(self, itemIdx) - - #---------------------------------------------------------------------------- - # Commands - - def ToggleExpansion(self, group): - """ - Toggle the expanded/collapsed state of the given group and redisplay the list - """ - self._DoExpandCollapse([group], not group.isExpanded) - - - def Expand(self, group): - """ - Expand the given group and redisplay the list - """ - self._DoExpandCollapse([group], True) - - - def Collapse(self, group): - """ - Collapse the given group and redisplay the list - """ - self._DoExpandCollapse([group], False) - - - def ExpandAll(self, groups=None): - """ - Expand the given groups (or all groups) and redisplay the list - """ - if groups is None: - groups = self.groups - self._DoExpandCollapse(groups, True) - - - def CollapseAll(self, groups=None): - """ - Collapse the given groups (or all groups) and redisplay the list - """ - if groups is None: - groups = self.groups - self._DoExpandCollapse(groups, False) - - - def _DoExpandCollapse(self, groups, isExpanding): - """ - Do the real work of expanding/collapsing the given groups - """ - # Cull groups that aren't going to change - groups = [x for x in groups if x.isExpanded != isExpanding] - if not groups: - return - - # Let the world know that the given groups are about to be expanded/collapsed - evt = OLVEvent.ExpandingCollapsingEvent(self, groups, isExpanding) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - return - - # Expand/contract the groups, then put those changes into effect - for x in evt.groups: - x.isExpanded = isExpanding - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - - # Refresh eveything from the first group down - i = min([self.GetIndexOf(x) for x in evt.groups]) - self.RefreshItems(i, len(self.innerList)-1) - - # Let the world know that the given groups have been expanded/collapsed - evt = OLVEvent.ExpandedCollapsedEvent(self, evt.groups, isExpanding) - self.GetEventHandler().ProcessEvent(evt) - - - def Reveal(self, modelObject): - """ - Ensure that the given modelObject is visible, expanding the group it belongs to, - if necessary - """ - # If it is already there, just make sure it is visible - i = self.GetIndexOf(modelObject) - if i != -1: - self.EnsureVisible(i) - return True - - # Find which group (if any) the object belongs to, and - # expand it and then try to reveal it again - for group in self.groups: - if not group.isExpanded and modelObject in group.modelObjects: - self.Expand(group) - return self.Reveal(modelObject) - - return False - - - def SelectAll(self): - """ - Selected all model objects in the control. - - In a GroupListView, this does not select blank lines or groups - """ - self.SetItemState(-1, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - for (i, x) in enumerate(self.innerList): - if x is None or isinstance(x, ListGroup): - self.SetItemState(i, 0, wx.LIST_STATE_SELECTED) - - # With the current implemetation, these are synonyms - SelectGroup = ObjectListView.SelectObject - SelectGroups = ObjectListView.SelectObjects - - - #---------------------------------------------------------------------------- - # Accessing - - def FindGroupFor(self, modelObject): - """ - Return the group that contains the given object or None if the given - object is not found - """ - for group in self.groups: - if modelObject in group.modelObjects: - return group - return None - - - def GetSelectedGroups(self): - """ - Return a list of the groups that are selected - """ - selectedGroups = list() - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - model = self.innerList[i] - if isinstance(model, ListGroup): - selectedGroups.append(model) - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - return selectedGroups - - - def GetFilteredObjects(self): - """ - Return the model objects that are actually displayed in the control. - """ - objects = list() - for x in self.groups: - objects.extend(x.modelObjects) - return objects - - - def GetObjectAt(self, index): - """ - Return the model object at the given row of the list. - - With GroupListView, this method can return None, since the given - index may be a blank row or a group header. These do not have - corresponding model objects. - """ - try: - model = self.innerList[index] - if isinstance(model, ListGroup): - model = None - except IndexError: - model = None - - return model - - - def YieldSelectedObjects(self): - """ - Progressively yield the selected modelObjects. - - Only return model objects, not blank lines or ListGroups - """ - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - model = self.GetObjectAt(i) - if model is not None: - yield model - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - - - def _CanUseBisect(self, searchColumn): - """ - Return True if we can use binary search on the given column. - - A GroupListView can never use binary search since its rows aren't sorted. - """ - return not self.showGroups - - - def _GetValuesAsMultiList(self, objects): - """ - Return a list of lists of the string of the aspects of the given objects - """ - cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the expand icon columns - objects = [x for x in objects if x is not None and not isinstance(x, ListGroup)] - return [[column.GetStringValue(x) for column in cols] for x in objects] - - - #---------------------------------------------------------------------------- - # Event handlers - - def _HandleChar(self, evt): - - if not self.IsCellEditing() and self.handleStandardKeys: - if (evt.GetKeyCode() == wx.WXK_LEFT): - self.CollapseAll(self.GetSelectedGroups()) - return - if (evt.GetKeyCode() == wx.WXK_RIGHT): - self.ExpandAll(self.GetSelectedGroups()) - return - - FastObjectListView._HandleChar(self, evt) - - - def _HandleColumnClick(self, evt): - """ - The user has clicked on a column title - """ - - # If they click on a new column, we have to rebuild our groups - if evt.GetColumn() != self.sortColumnIndex: - self.groups = None - - FastObjectListView._HandleColumnClick(self, evt) - - - def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): - """ - Handle a left click on the image at the given cell - """ - self._PossibleFinishCellEdit() - - listObject = self.innerList[rowIndex] - if subItemIndex == 0 and isinstance(listObject, ListGroup): - self.ToggleExpansion(listObject) - else: - FastObjectListView._HandleLeftDownOnImage(self, rowIndex, subItemIndex) - - - #---Sorting-------------------------------------------------------#000000#FFFFFF - - def SortGroups(self, groups=None, ascending=None): - """ - Sort the given collection of groups in the given direction (defaults to ascending). - - The model objects within each group will be sorted as well - """ - if groups is None: - groups = self.groups - if ascending is None: - ascending = self.sortAscending - - # If the groups are locked, we sort by the sort column, otherwise by the grouping column. - # The primary column is always used as a secondary sort key. - if self.GetAlwaysGroupByColumn(): - sortCol = self.GetSortColumn() - else: - sortCol = self.GetGroupByColumn() - - # Let the world have a change to sort the items - evt = OLVEvent.SortGroupsEvent(self, groups, sortCol, ascending) - self.GetEventHandler().ProcessEvent(evt) - if evt.wasHandled: - return - - # Sorting event wasn't handled, so we do the default sorting - def _getLowerCaseKey(group): - try: - return group.key.lower() - except: - return group.key - - groups.sort(key=_getLowerCaseKey, reverse=(not ascending)) - - # Sort the model objects within each group. - for x in groups: - self._SortObjects(x.modelObjects, sortCol, self.GetPrimaryColumn()) - - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - GroupListViews don't sort the items directly. We have to sort the groups - and then rebuild the list. - """ - if not self.showGroups: - return FastObjectListView._SortItemsNow(self) - - if self.groups is None: - self.groups = self._BuildGroups() - self.SortGroups(self.groups) - self._SetGroups(self.groups) - - - #def _FormatAllRows(self): - # """ - # GroupListViews don't need this method. - # """ - # pass - - - -####################################################################### - -class ListGroup(object): - """ - A ListGroup is a partition of model objects that can be presented - under a collapsible heading in a GroupListView. - """ - - def __init__(self, key, title, isExpanded=True): - self.key = key - self.title = title - - self.isExpanded = isExpanded - self.modelObjects = list() - - - def Add(self, model): - """ - Add the given model to those that belong to this group. - """ - self.modelObjects.append(model) - - -####################################################################### - -class ColumnDefn(object): - """ - A ColumnDefn controls how one column of information is sourced and formatted. - - Much of the intelligence and ease of use of an ObjectListView comes from the column - definitions. It is worthwhile gaining an understanding of the capabilities of this class. - - Public Attributes (alphabetically): - - * align - How will the title and the cells of the this column be aligned. Possible - values: 'left', 'centre', 'right' - - * cellEditorCreator - This is a callable that will be invoked to create an editor for value in this - column. The callable should accept three parameters: the objectListView starting - the edit, the rowIndex and the subItemIndex. It should create and return a Control - that is capable of editing the value. - - If this is None, a cell editor will be chosen based on the type of objects in this - column (See CellEditor.EditorRegistry). - - * freeSpaceProportion - If the column is space filling, this attribute controls what proportion of the - space should be given to this column. By default, all spacing filling column share - the free space equally. By changing this attribute, a column can be given a larger - proportion of the space. - - * groupKeyConverter - The groupKeyConverter converts a group key into the string that can be presented - to the user. This string will be used as part of the title for the group. - - Its behaviour is the same as "stringConverter." - - * groupKeyGetter - When this column is used to group the model objects, what groupKeyGetter extracts - the value from each model that will be used to partition the model objects into - groups. - - Its behaviour is the same as "valueGetter." - - If this is None, the value returned by valueGetter will be used. - - * groupTitleSingleItem - When a group is created that contains a single item, and the GroupListView - has "showItemCounts" turned on, this string will be used to create the title - of the group. The string should contain two placeholder: %(title)s and %(count)d. - Example: "%(title)s [only %(count)d song]" - - * groupTitlePluralItems - When a group is created that contains 0 items or >1 items, and the GroupListView - has "showItemCounts" turned on, this string will be used to create the title - of the group. The string should contain two placeholder: %(title)s and %(count)d. - Example: "%(title)s [%(count)d songs]" - - * headerImage - The index or name of the image that will be shown against the column header. - Remember, a column header can only show one image at a time, so if the column - is the sort column, it will show the sort indicator -- not this headerImage. - - * imageGetter - A string, callable or integer that is used to get a index of the image to be - shown in a cell. - - Strings and callable are used as for the `valueGetter` attribute. - - Integers are treated as constants (that is, all rows will have the same - image). - - * isEditable - Can the user edit cell values in this column? Default is True - - * isSearchable - If this column is the sort column, when the user types into the ObjectListView, - will a match be looked for using values from this column? If this is False, - values from column 0 will be used. - Default is True. - - * isSpaceFilling - Is this column a space filler? Space filling columns resize to occupy free - space within the listview. As the listview is expanded, space filling columns - expand as well. Conversely, as the control shrinks these columns shrink too. - - Space filling columns can disappear (i.e. have a width of 0) if the control - becomes too small. You can set `minimumWidth` to prevent them from - disappearing. - - * maximumWidth - An integer indicate the number of pixels above which this column will not resize. - Default is -1, which means there is no limit. - - * minimumWidth - An integer indicate the number of pixels below which this column will not resize. - Default is -1, which means there is no limit. - - * useBinarySearch - If isSearchable and useBinarySearch are both True, the ObjectListView will use a - binary search algorithm to locate a match. If useBinarySearch is False, a simple - linear search will be done. - - The binary search can quickly search large numbers of rows (10,000,000 in about 25 - comparisons), which makes them ideal for virtual lists. However, there are two - constraints: - - - the ObjectListView must be sorted by this column - - - sorting by string representation must give the same ordering as sorting - by the aspect itself. - - The second constraint is necessary because the user types characters expecting - them to match the string representation of the data. The binary search will make - its decisions using the string representation, but the rows ordered - by aspect value. This will only work if sorting by string representation - would give the same ordering as sorting by the aspect value. - - In general, binary searches work with strings, YYYY-MM-DD dates, and booleans. - They do not work with numerics or other date formats. - - If either of these constrains are not true, you must set - useBinarySearch to False and be content with linear searches. Otherwise, the - searching will not work correctly. - - * stringConverter - A string or a callable that will used to convert a cells value into a presentation - string. - - If it is a callble, it will be called with the value for the cell and must return - a string. - - If it is a string, it will be used as a format string with the % operator, e.g. - "self.stringConverter % value." For dates and times, the stringConverter will be - passed as the first parameter to the strftime() method on the date/time. - - * title - A string that will be used as the title of the column in the listview - - * valueGetter - A string, callable or integer that is used to get the value to be displayed in - a cell. See _Munge() for details on how this attribute is used. - - A callable is simply called and the result is the value for the cell. - - The string can be the name of a method to be invoked, the name of an attribute - to be fetched, or (for dictionary like objects) an index into the dictionary. - - An integer can only be used for list-like objects and is used as an index into - the list. - - * valueSetter - A string, callable or integer that is used to write an edited value back into the - model object. - - A callable is called with the model object and the new value. Example:: - - myCol.valueSetter(modelObject, newValue) - - An integer can only be used if the model object is a mutable sequence. The integer - is used as an index into the list. Example:: - - modelObject[myCol.valueSetter] = newValue - - The string can be: - - * the name of a method to be invoked, in which case the method should accept the - new value as its parameter. Example:: - - method = getattr(modelObject, myCol.valueSetter) - method(newValue) - - * the name of an attribute to be updated. This attribute will not be created: it - must already exist. Example:: - - setattr(modelObject, myCol.valueSetter, newValue) - - * for dictionary like model objects, an index into the dictionary. Example:: - - modelObject[myCol.valueSetter] = newValue - - * useInitialLetterForGroupKey - When this is true and the group key for a row is a string, only the first letter of - the string will be considered as the group key. This is often useful for grouping - row when the column contains a name. - - * width - How many pixels wide will the column be? -1 means auto size to contents. For a list with - thousands of items, autosize can be noticably slower than specifically setting the size. - - - The `title`, `align` and `width` attributes are only references when the column definition is given to the - ObjectListView via the `SetColumns()` or `AddColumnDefn()` methods. The other attributes are referenced - intermittently -- changing them will change the behaviour of the `ObjectListView`. - - Without a string converter, None will be converted to an empty string. Install a string converter ('%s' - will suffice) if you want to see the 'None' instead. - - BUG: Double-clicking on a divider (under Windows) can resize a column beyond its minimum and maximum widths. - """ - - def __init__(self, title="title", align="left", width=-1, - valueGetter=None, imageGetter=None, stringConverter=None, valueSetter=None, isEditable=True, - fixedWidth=None, minimumWidth=-1, maximumWidth=-1, isSpaceFilling=False, - cellEditorCreator=None, autoCompleteCellEditor=False, autoCompleteComboBoxCellEditor=False, - checkStateGetter=None, checkStateSetter=None, - isSearchable=True, useBinarySearch=None, headerImage=-1, - groupKeyGetter=None, groupKeyConverter=None, useInitialLetterForGroupKey=False, - groupTitleSingleItem=None, groupTitlePluralItems=None): - """ - Create a new ColumnDefn using the given attributes. - - The attributes behave as described in the class documentation, except for: - - * fixedWidth - An integer which indicates that this column has the given width and is not resizable. - Useful for column that always display fixed with data (e.g. a single icon). Setting this - parameter overrides the width, minimumWidth and maximumWidth parameters. - - * autoCompleteCellEditor - If this is True, the column will use an autocomplete TextCtrl when - values of this column are edited. This overrules the cellEditorCreator parameter. - - * autoCompleteComboBoxCellEditor - If this is True, the column will use an autocomplete ComboBox when - values of this column are edited. This overrules the cellEditorCreator parameter. - """ - self.title = title - self.align = align - self.valueGetter = valueGetter - self.imageGetter = imageGetter - self.stringConverter = stringConverter - self.valueSetter = valueSetter - self.isSpaceFilling = isSpaceFilling - self.cellEditorCreator = cellEditorCreator - self.freeSpaceProportion = 1 - self.isEditable = isEditable - self.isSearchable = isSearchable - self.useBinarySearch = useBinarySearch - self.headerImage = headerImage - self.groupKeyGetter = groupKeyGetter - self.groupKeyConverter = groupKeyConverter - self.useInitialLetterForGroupKey = useInitialLetterForGroupKey - self.groupTitleSingleItem = groupTitleSingleItem or "%(title)s (%(count)d item)" - self.groupTitlePluralItems = groupTitlePluralItems or "%(title)s (%(count)d items)" - self.isInternal = False # was this column created internally by ObjectListView? - - self.minimumWidth = minimumWidth - self.maximumWidth = maximumWidth - self.width = self.CalcBoundedWidth(width) - - if fixedWidth is not None: - self.SetFixedWidth(fixedWidth) - - if autoCompleteCellEditor: - self.cellEditorCreator = lambda olv, row, col: CellEditor.MakeAutoCompleteTextBox(olv, col) - - if autoCompleteComboBoxCellEditor: - self.cellEditorCreator = lambda olv, row, col: CellEditor.MakeAutoCompleteComboBox(olv, col) - - self.checkStateGetter = checkStateGetter - self.checkStateSetter = checkStateSetter - - #------------------------------------------------------------------------------- - # Column properties - - def GetAlignment(self): - """ - Return the alignment that this column uses - """ - alignment = { - "l": wx.LIST_FORMAT_LEFT, - "c": wx.LIST_FORMAT_CENTRE, - "r": wx.LIST_FORMAT_RIGHT - }.get(self.align[:1], wx.LIST_FORMAT_LEFT) - - return alignment - - def GetAlignmentForText(self): - """ - Return the alignment of this column in a form that can be used as - a style flag on a text control - """ - return { - "l": wx.TE_LEFT, - "c": wx.TE_CENTRE, - "r": wx.TE_RIGHT, - }.get(self.align[:1], wx.TE_LEFT) - - #------------------------------------------------------------------------------- - # Value accessing - - def GetValue(self, modelObject): - """ - Return the value for this column from the given modelObject - """ - return self._Munge(modelObject, self.valueGetter) - - - def GetStringValue(self, modelObject): - """ - Return a string representation of the value for this column from the given modelObject - """ - value = self.GetValue(modelObject) - return self._StringToValue(value, self.stringConverter) - - - def _StringToValue(self, value, converter): - """ - Convert the given value to a string, using the given converter - """ - try: - return converter(value) - except TypeError: - pass - - if converter and isinstance(value, (datetime.datetime, datetime.date, datetime.time)): - return value.strftime(self.stringConverter) - - # By default, None is changed to an empty string. - if not converter and not value: - return "" - - fmt = converter or "%s" - try: - return fmt % value - except UnicodeError: - return unicode(fmt) % value - - - def GetGroupKey(self, modelObject): - """ - Return the group key for this column from the given modelObject - """ - if self.groupKeyGetter is None: - key = self.GetValue(modelObject) - else: - key = self._Munge(modelObject, self.groupKeyGetter) - if self.useInitialLetterForGroupKey: - try: - return key[:1].upper() - except TypeError: - return key - else: - return key - - - def GetGroupKeyAsString(self, groupKey): - """ - Return the given group key as a human readable string - """ - # If there is no group key getter, we must have the normal aspect value. So if - # there isn't a special key converter, use the normal aspect to string converter. - if self.groupKeyGetter is None and self.groupKeyConverter is None: - return self._StringToValue(groupKey, self.stringConverter) - else: - return self._StringToValue(groupKey, self.groupKeyConverter) - - - def GetGroupTitle(self, group, useItemCount): - """ - Return a title of the group - """ - title = self.GetGroupKeyAsString(group.key) - if useItemCount: - objectCount = len(group.modelObjects) - if objectCount == 1: - fmt = self.groupTitleSingleItem - else: - fmt = self.groupTitlePluralItems - title = fmt % {"title":title, "count":objectCount} - return title - - - def GetImage(self, modelObject): - """ - Return the image index for this column from the given modelObject. -1 means no image. - """ - if self.imageGetter is None: - return -1 - - if isinstance(self.imageGetter, int): - return self.imageGetter - - idx = self._Munge(modelObject, self.imageGetter) - if idx is None: - return -1 - else: - return idx - - - def SetValue(self, modelObject, value): - """ - Set this columns aspect of the given modelObject to have the given value. - """ - if self.valueSetter is None: - return self._SetValueUsingMunger(modelObject, value, self.valueGetter, False) - else: - return self._SetValueUsingMunger(modelObject, value, self.valueSetter, True) - - - def _SetValueUsingMunger(self, modelObject, value, munger, shouldInvokeCallable): - """ - Look for ways to update modelObject with value using munger. If munger finds a - callable, it will be called if shouldInvokeCallable == True. - """ - # If there isn't a munger, we can't do anything - if munger is None: - return - - # Is munger a function? - if callable(munger): - if shouldInvokeCallable: - munger(modelObject, value) - return - - # Try indexed access for dictionary or list like objects - try: - modelObject[munger] = value - return - except: - pass - - # Is munger the name of some slot in the modelObject? - try: - attr = getattr(modelObject, munger) - except TypeError: - return - except AttributeError: - return - - # Is munger the name of a method? - if callable(attr): - if shouldInvokeCallable: - attr(value) - return - - # If we get to here, it seems that munger is the name of an attribute or - # property on modelObject. Try to set, realising that many things could still go wrong. - try: - setattr(modelObject, munger, value) - except: - pass - - - def _Munge(self, modelObject, munger): - """ - Wrest some value from the given modelObject using the munger. - With a description like that, you know this method is going to be obscure :-) - - 'munger' can be: - - 1) a callable. - This method will return the result of executing 'munger' with 'modelObject' as its parameter. - - 2) the name of an attribute of the modelObject. - If that attribute is callable, this method will return the result of executing that attribute. - Otherwise, this method will return the value of that attribute. - - 3) an index (string or integer) onto the modelObject. - This allows dictionary-like objects and list-like objects to be used directly. - """ - if munger is None: - return None - - # THINK: The following code treats an instance variable with the value of None - # as if it doesn't exist. Is that best? - - # Try attribute access - try: - attr = getattr(modelObject, munger, None) - if attr is not None: - try: - return attr() - except TypeError: - return attr - except TypeError: - # Happens when munger is not a string - pass - - # Use the callable directly, if possible. - # In accordance with Guido's rules for Python 3, we just call it and catch the - # exception - try: - return munger(modelObject) - except TypeError: - pass - - # Try dictionary-like indexing - try: - return modelObject[munger] - except: - return None - - #------------------------------------------------------------------------------- - # Width management - - def CalcBoundedWidth(self, width): - """ - Calculate the given width bounded by the (optional) minimum and maximum column widths - """ - - # Values of < 0 have special meanings, so just return them - if width < 0: - return width - - if self.maximumWidth >= 0: - width = min(self.maximumWidth, width) - return max(self.minimumWidth, width) - - - def IsFixedWidth(self): - """ - Is this column fixed width? - """ - return self.minimumWidth != -1 and \ - self.maximumWidth != -1 and \ - (self.minimumWidth >= self.maximumWidth) - - - def SetFixedWidth(self, width): - """ - Make this column fixed width - """ - self.width = self.minimumWidth = self.maximumWidth = width - - #---------------------------------------------------------------------------- - # Check state - - def HasCheckState(self): - """ - Return if this column is showing a check box? - """ - return self.checkStateGetter is not None - - - def GetCheckState(self, modelObject): - """ - Return the check state of the given model object - """ - if self.checkStateGetter is None: - return None - else: - return self._Munge(modelObject, self.checkStateGetter) - - - def SetCheckState(self, modelObject, state): - """ - Set the check state of the given model object - """ - if self.checkStateSetter is None: - return self._SetValueUsingMunger(modelObject, state, self.checkStateGetter, False) - else: - return self._SetValueUsingMunger(modelObject, state, self.checkStateSetter, True) - -#====================================================================== - -class NamedImageList(object): - """ - A named image list is an Adaptor that gives a normal image list - the ability to reference images by name, rather than just index - """ - - def __init__(self, imageList=None, imageSize=16): - """ - """ - self.imageList = imageList or wx.ImageList(imageSize, imageSize) - self.imageSize = imageSize - self.nameToImageIndexMap = {} - - - def GetSize(self, ignored=None): - """ - Return a pair that represents the size of the image in this list - """ - # Mac and Linux have trouble getting the size of empty image lists - if self.imageList.GetImageCount() == 0: - return (self.imageSize, self.imageSize) - else: - return self.imageList.GetSize(0) - - - def AddNamedImage(self, name, image): - """ - Add the given image to our list, and remember its name. - Returns the images index. - """ - imageIndex = self.imageList.Add(image) - if name is not None: - self.nameToImageIndexMap[name] = imageIndex - return imageIndex - - - def HasName(self, name): - """ - Does this list have an image with the given name?" - """ - return name in self.nameToImageIndexMap - - - def GetImageIndex(self, name): - """ - Return the image with the given name, or -1 if it doesn't exist - """ - return self.nameToImageIndexMap.get(name, -1) - -#====================================================================== - -class BatchedUpdate(object): - """ - This class is an *Adapter* around an ``ObjectListView`` which ensure that the list is updated, at most, - once every *N* seconds. - - Usage:: - - self.olv2 = BatchedUpdate(self.olv, 3) - # Now use olv2 in place of olv, and the list will only be updated at most once - # every 3 second, no many how many calls are made to it. - - This is useful for a certain class of problem where model objects are update frequently -- more - frequently than you wish to update the control. A backup program may be able to backup several - files a second, but does not wish to update the list ctrl that often. A packet sniffer will - receive hundreds of packets per second, but should not try to update the list ctrl for each - packet. A batched update adapter solves situations like these in a trivial manner. - - This class only intercepts the following messages: - * ``AddObject()``, ``AddObjects()`` - * ``RefreshObject()``, ``RefreshObjects()`` - * ``RemoveObject()``, ``RemoveObjects()`` - * ``RepopulateList()`` - * ``SetObjects()`` - - All other message are passed directly to the ``ObjectListView`` and are thus unbatched. This means - that sorting and changes to columns are unbatched and will take effect immediately. - - You need to be a little careful when using batched updates. There are at least two things - you need to avoid, or at least be careful about: - - 1) Don't mix batched and unbatched updates. If you go behind the back of the batched update - wrapper and make direct changes to the underlying control, you will probably get bitten by - difficult-to-reproduce bugs. For example:: - - self.olvBatched.SetObjects(objects) # Batched update - self.olvBatched.objectlistView.AddObject(aModel) # unbatched update - - This will almost certainly not do what you expect, or at best, will only sometimes do - what you want. - - 2) You cannot assume that objects will immediately appear in the list and - thus be available for further operations. For example:: - - self.olv.AddObject(aModel) - self.olv.Check(aModel) - - If *self.olv* is a batched update adapter, this code *may* not work since the - ``AddObject()`` might not have yet taken effect, so the ``Check()`` will not find - *aModel* in the control. Worse, it may work most of the time and fail only occassionally. - - If you need to be able to do further processing on objects just added, it would be better - not to use a batched adapter. - - """ - - # For SetObjects(), None and empty list are both possible valid values so we need a - # non-valid value that indicates that SetObjects() has not been called - NOT_SET = -1 - - def __init__(self, objectListView, updatePeriod=0): - self.objectListView = objectListView # Must not be None - self.updatePeriod = updatePeriod - - self.objectListView.Bind(wx.EVT_IDLE, self._HandleIdle) - - self.newModelObjects = BatchedUpdate.NOT_SET - self.objectsToAdd = list() - self.objectsToRefresh = list() - self.objectsToRemove = list() - self.freezeUntil = 0 - - - def __getattr__(self, name): - """ - Forward any unknown references to the original objectListView. - - This is what allows us to pretend to be an ObjectListView. - """ - return getattr(self.objectListView, name) - - - def RepopulateList(self): - """ - Remember the given model objects so that they can be displayed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.RepopulateList() - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.newModelObjects = self.objectListView.modelObjects - self.objectsToRefresh = list() - - # Unlike SetObjects(), refreshing the list does NOT invalidate the objects to be added/removed - - - def SetObjects(self, modelObjects): - """ - Remember the given model objects so that they can be displayed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.SetObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.newModelObjects = modelObjects - # Explicitly setting the objects to be shown renders void any previous Add/Refresh/Remove commands - self.objectsToAdd = list() - self.objectsToRefresh = list() - self.objectsToRemove = list() - - - def AddObject(self, modelObject): - """ - Add the given object to our collection of objects. - - The object will appear at its sorted location, or at the end of the list if - the list is unsorted - """ - self.AddObjects([modelObject]) - - - def AddObjects(self, modelObjects): - """ - Remember the given model objects so that they can be added when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.AddObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - # TODO: We should check that none of the model objects is already in the list - self.objectsToAdd.extend(modelObjects) - - # Since we are adding these objects, we must no longer remove them - if self.objectsToRemove: - for x in modelObjects: - self.objectsToRemove.remove(x) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given model - """ - self.RefreshObjects([modelObject]) - - - def RefreshObjects(self, modelObjects): - """ - Refresh the information displayed about the given model objects - """ - if self.freezeUntil < time.clock(): - self.objectListView.RefreshObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.objectsToRefresh.extend(modelObjects) - - - def RemoveObject(self, modelObjects): - """ - Remember the given model objects so that they can be removed when the next update cycle occurs - """ - self.RemoveObjects([modelObject]) - - - def RemoveObjects(self, modelObjects): - """ - Remember the given model objects so that they can be removed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.RemoveObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.objectsToRemove.extend(modelObjects) - - # Since we are removing these objects, we must no longer add them - if self.objectsToAdd: - for x in modelObjects: - self.objectsToAdd.remove(x) - - #---------------------------------------------------------------------------- - # Event processing - - def _HandleIdle(self, evt): - """ - The app is idle. Process any outstanding requests - """ - if (self.newModelObjects != BatchedUpdate.NOT_SET or - self.objectsToAdd or - self.objectsToRefresh or - self.objectsToRemove): - if self.freezeUntil < time.clock(): - self._ApplyChanges() - else: - evt.RequestMore() - - def _ApplyChanges(self): - """ - Apply any batched changes to the list - """ - if self.newModelObjects != BatchedUpdate.NOT_SET: - self.objectListView.SetObjects(self.newModelObjects) - - if self.objectsToAdd: - self.objectListView.AddObjects(self.objectsToAdd) - - if self.objectsToRemove: - self.objectListView.RemoveObjects(self.objectsToRemove) - - if self.objectsToRefresh: - self.objectListView.RefreshObjects(self.objectsToRefresh) - - self.newModelObjects = BatchedUpdate.NOT_SET - self.objectsToAdd = list() - self.objectsToRemove = list() - self.objectsToRefresh = list() - self.freezeUntil = time.clock() + self.updatePeriod - -#---------------------------------------------------------------------------- -# Built in images so clients don't have to do the same - -import cStringIO, zlib - -def _getSmallUpArrowData(): - return zlib.decompress( -'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\ -\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4[z\xba8\x86X\xf4&\ -\xa7\xa4$\xa5-`1\x08\\R\xcd"\x11\x10\x1f\xfe{~\x0es\xc2,N\xc9\xa6\xab\x0c%\ -\xbe?x\x0e\x1a0LO\x8ay\xe4sD\xe3\x90\xfay\x8bYB\xec\x8d\x8c\x0c\xc1\x01b9\ -\xe1\xbc\x8fw\x01\ra\xf0t\xf5sY\xe7\x94\xd0\x04\x00\xb7\x89#\xbb' ) - -def _getSmallUpArrowBitmap(): - stream = cStringIO.StringIO(_getSmallUpArrowData()) - return wx.BitmapFromImage(wx.ImageFromStream(stream)) - -def _getSmallDownArrowData(): - return zlib.decompress( -'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\ -\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\x07x\xba8\x86X\xf4\ -&\xa7\xa4$\xa5-`1\x08\\R}\x85\x81\r\x04\xc4R\xfcjc\xdf\xd6;II\xcd\x9e%Y\xb8\ -\x8b!v\xd2\x844\x1e\xe6\x0f\x92M\xde2\xd9\x12\x0b\xb4\x8f\xbd6rSK\x9b\xb3c\ -\xe1\xc2\x87\xf6v\x95@&\xdb\xb1\x8bK|v22,W\x12\xd0\xdb-\xc4\xe4\x044\x9b\xc1\ -\xd3\xd5\xcfe\x9dSB\x13\x00$1+:' ) - -def _getSmallDownArrowBitmap(): - stream = cStringIO.StringIO(_getSmallDownArrowData()) - return wx.BitmapFromImage(wx.ImageFromStream(stream)) - - -# -####################################################################### -# TESTING ONLY - -if __name__ == '__main__': - pass diff --git a/odmtools/lib/oldOlv/README.md b/odmtools/lib/oldOlv/README.md deleted file mode 100644 index 3ddd068..0000000 --- a/odmtools/lib/oldOlv/README.md +++ /dev/null @@ -1,6 +0,0 @@ -!!Updated ObjectListView - -* Resolves "NameError: global name 'rowModel' is not defined" -* Resolves "wxPyDeprecationWarning: Accessing deprecated property" warning -* Resolves Mac/Linux undefined row number error -* Removing rows don't flicker as much \ No newline at end of file diff --git a/odmtools/lib/oldOlv/WordWrapRenderer.py b/odmtools/lib/oldOlv/WordWrapRenderer.py deleted file mode 100644 index c4dd1c9..0000000 --- a/odmtools/lib/oldOlv/WordWrapRenderer.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: WordWrapRenderer.py -# Author: Phillip Piper -# Created: 25 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/25 JPP Initial version -#---------------------------------------------------------------------------- -# To do: - -""" -A WordWrapRenderer encapsulates the ability to draw and measure word wrapped -strings directly to a device context. - -It is meant to be good enough for general use. It is not suitable for typographic layout --- it does not handle kerning or ligatures. - -The DC passed to these methods cannot be a GraphicContext DC. These methods use -GetPartialTextExtents() which does not work with GCDC's (as of wx 2.8). - -""" - -import wx -import bisect -from wx.lib.wordwrap import wordwrap - -class WordWrapRenderer: - """ - This renderer encapsulates the logic need to draw and measure a word-wrapped - string within a given rectangle. - """ - - #---------------------------------------------------------------------------- - # Calculating - - @staticmethod - def CalculateHeight(dc, text, width): - """ - Calculate the height of the given text when fitted within the given width. - - Remember to set the font on the dc before calling this method. - """ - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, width, dc, True) - (width, height, descent, externalLeading) = dc.GetFullTextExtent("Wy") - return (lines.count("\n")+1) * (height + externalLeading) - - - #---------------------------------------------------------------------------- - # Rendering - - @staticmethod - def DrawString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, allowClipping=False): - """ - Draw the given text word-wrapped within the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - # DrawLabel only accepts a wx.Rect - try: - bounds = wx.Rect(*bounds) - except: - pass - - if allowClipping: - clipper = wx.DCClipper(dc, bounds) - - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, bounds[2], dc, True) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def DrawTruncatedString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, ellipse=wx.RIGHT, ellipseChars="..."): - """ - Draw the given text truncated to the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - try: - bounds = wx.Rect(*bounds) - except: - pass - lines = WordWrapRenderer._Truncate(dc, text, bounds[2], ellipse, ellipseChars) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def _Truncate(dc, text, maxWidth, ellipse, ellipseChars): - """ - Return a string that will fit within the given width. - """ - line = text.split("\n")[0] # only consider the first line - if not line: - return "" - - pte = dc.GetPartialTextExtents(line) - - # Does the whole thing fit within our given width? - stringWidth = pte[-1] - if stringWidth <= maxWidth: - return line - - # We (probably) have to ellipse the text so allow for ellipse - maxWidthMinusEllipse = maxWidth - dc.GetTextExtent(ellipseChars)[0] - - if ellipse == wx.LEFT: - i = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse) - return ellipseChars + line[i+1:] - - if ellipse == wx.CENTER: - i = bisect.bisect(pte, maxWidthMinusEllipse / 2) - j = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse / 2) - return line[:i] + ellipseChars + line[j+1:] - - if ellipse == wx.RIGHT: - i = bisect.bisect(pte, maxWidthMinusEllipse) - return line[:i] + ellipseChars - - # No ellipsing, just truncating is the default - i = bisect.bisect(pte, maxWidth) - return line[:i] - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - - class TestPanel(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent, -1, style=wx.FULL_REPAINT_ON_RESIZE) - self.Bind(wx.EVT_PAINT, self.OnPaint) - - self.text = """This is Thisisareallylongwordtoseewhathappens the text to be drawn. It needs to be long to see if wrapping works. to long words. -This is on new line by itself. - -This should have a blank line in front of it but still wrap when we reach the edge. - -The bottom of the red rectangle should be immediately below this.""" - self.font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Gill Sans") - - def OnPaint(self, evt): - dc = wx.PaintDC(self) - inset = (20, 20, 20, 20) - rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - - # Calculate exactly how high the wrapped is going to be and put a frame around it. - dc.SetFont(self.font) - dc.SetPen(wx.RED_PEN) - rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) - dc.DrawRectangle(*rect) - WordWrapRenderer.DrawString(dc, self.text, rect, wx.ALIGN_LEFT) - #WordWrapRenderer.DrawTruncatedString(dc, self.text, rect, wx.ALIGN_CENTER_HORIZONTAL,s ellipse=wx.CENTER) - - #bmp = wx.EmptyBitmap(rect[0]+rect[2], rect[1]+rect[3]) - #mdc = wx.MemoryDC(bmp) - #mdc.SetBackground(wx.Brush("white")) - #mdc.Clear() - #mdc.SetFont(self.font) - #mdc.SetPen(wx.RED_PEN) - #rect[3] = WordWrapRenderer.CalculateHeight(mdc, self.text, rect[2]) - #mdc.DrawRectangle(*rect) - #WordWrapRenderer.DrawString(mdc, self.text, rect, wx.ALIGN_LEFT) - #del mdc - #dc = wx.ScreenDC() - #dc.DrawBitmap(bmp, 20, 20) - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.testPanel = TestPanel(self.panel) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.testPanel, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/__init__.py b/odmtools/lib/oldOlv/__init__.py deleted file mode 100644 index 433e1b5..0000000 --- a/odmtools/lib/oldOlv/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: ObjectListView module initialization -# Author: Phillip Piper -# Created: 29 February 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/02 JPP Added list printing material -# 2008/07/24 JPP Added list group related material -# 2008/06/19 JPP Added sort event related material -# 2008/04/11 JPP Initial Version - -""" -An ObjectListView provides a more convienent and powerful interface to a ListCtrl. -""" - -__version__ = '1.2' -__copyright__ = "Copyright (c) 2008 Phillip Piper (phillip_piper@bigfoot.com)" - -from ObjectListView import ObjectListView, VirtualObjectListView, ColumnDefn, FastObjectListView, GroupListView, ListGroup, BatchedUpdate -from OLVEvent import CellEditFinishedEvent, CellEditFinishingEvent, CellEditStartedEvent, CellEditStartingEvent, SortEvent -from OLVEvent import EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHED, EVT_SORT -from OLVEvent import EVT_COLLAPSING, EVT_COLLAPSED, EVT_EXPANDING, EVT_EXPANDED, EVT_GROUP_CREATING, EVT_GROUP_SORT -from CellEditor import CellEditorRegistry, MakeAutoCompleteTextBox, MakeAutoCompleteComboBox -from ListCtrlPrinter import ListCtrlPrinter, ReportFormat, BlockFormat, LineDecoration, RectangleDecoration, ImageDecoration - -import Filter -__all__ = [ - "BatchedUpdate", - "BlockFormat", - "CellEditFinishedEvent", - "CellEditFinishingEvent", - "CellEditorRegistry", - "CellEditStartedEvent", - "CellEditStartingEvent", - "ColumnDefn", - "EVT_CELL_EDIT_FINISHED", - "EVT_CELL_EDIT_FINISHING", - "EVT_CELL_EDIT_STARTED", - "EVT_CELL_EDIT_STARTING", - "EVT_COLLAPSED", - "EVT_COLLAPSING", - "EVT_EXPANDED", - "EVT_EXPANDING", - "EVT_GROUP_CREATING", - "EVT_GROUP_SORT" - "EVT_SORT", - "Filter", - "FastObjectListView", - "GroupListView", - "ListGroup", - "ImageDecoration", - "MakeAutoCompleteTextBox", - "MakeAutoCompleteComboBox", - "ListGroup", - "ObjectListView", - "ListCtrlPrinter", - "RectangleDecoration", - "ReportFormat", - "SortEvent", - "VirtualObjectListView", -] diff --git a/odmtools/lib/oldOlv/t.py b/odmtools/lib/oldOlv/t.py deleted file mode 100644 index f3a546c..0000000 --- a/odmtools/lib/oldOlv/t.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Initial version -#---------------------------------------------------------------------------- -# To do: -# - scaling -# - gradients -# - images -# - attributes from ListCtrl -# - persistence of ReportFormat -# - use wx.wordwrap and DrawLabel -# - investigate DrawImageLabel - -""" -An ListCtrlPrinter takes an ObjectListView and turns it into a pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -""" - -import wx - -#====================================================================== - -class TestPrinter(wx.Printout): - - def __init__(self, margins=None): - """ - """ - wx.Printout.__init__(self, "The title") - - self.printData = wx.PrintData() - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetOrientation(wx.PORTRAIT) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - self.printData.SetNoCopies(1) - self.margins = margins or (wx.Point(0, 0), wx.Point(0, 0)) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - return page <= 3 - - def GetPageInfo(self): - return (1, 3, 1, 1) - - #---------------------------------------------------------------------------- - # Commands - - - def PageSetup(self): - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(None, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), - data.GetMarginBottomRight()) - dlg.Destroy() - - def PrintPreview(self, parent=None, title="ObjectListView Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - data = wx.PrintDialogData(self.printData) - t = TestPrinter(self.margins) - t2 = TestPrinter(self.margins) - self.preview = wx.PrintPreview(t, t2, data) - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent=None): - """ - Send the report to the configured printer - """ - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - - printout.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - print "OnPreparePrinting" - print "self.GetDC() = %s" % self.GetDC() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - print "OnBeginDocument(%d, %d)" % (start, end) - if not super(TestPrinter, self).OnBeginDocument(start, end): - return False - - return True - - def OnEndDocument(self): - print "OnEndDocument" - super(TestPrinter, self).OnEndDocument() - - def OnBeginPrinting(self): - print "OnBeginPrinting" - super(TestPrinter, self).OnBeginPrinting() - - def OnEndPrinting(self): - print "OnEndPrinting" - super(TestPrinter, self).OnEndPrinting() - - def OnPrintPage(self, page): - print "OnPrintPage(%d)" % page - dc = self.GetDC() - self.CalculateScale(dc) - self.CalculateLayout(dc) - dc.SetPen(wx.BLACK_PEN) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - bounds = (self.x1, self.y1, self.x2-self.x1, self.y2-self.y1) - print bounds - print self.pageHeight - dc.DrawRectangle(*bounds) - font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL) - dc.SetFont(font) - dc.DrawText("this is a string", bounds[0], bounds[1]) - - def CalculateScale(self, dc): - # Scaling the DC to screen size - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logScale = float(ppiPrinterX)/float(ppiScreenX) - pw, ph = self.GetPageSizePixels() # Adjusting scale - dw, dh = dc.GetSize() - scale = logScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4) - - def CalculateLayout(self, dc): - topLeft, bottomRight = self.margins - dw, dh = dc.GetSize() - self.x1 = topLeft.x * self.logUnitsMM - self.y1 = topLeft.y * self.logUnitsMM - self.x2 = dc.DeviceToLogicalYRel(dw) - bottomRight.x * self.logUnitsMM - self.y2 = dc.DeviceToLogicalYRel(dh) - bottomRight.y * self.logUnitsMM - self.pageHeight = self.y2 - self.y1 - 2*self.logUnitsMM - - - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - import wx - - # Where can we find the Example module? - import sys - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.olv = wx.ListCtrl(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.olv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - wx.CallLater(50, self.run) - - def run(self): - printer = TestPrinter() - printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/timingTests.txt b/odmtools/lib/oldOlv/timingTests.txt deleted file mode 100644 index 1e80d0d..0000000 --- a/odmtools/lib/oldOlv/timingTests.txt +++ /dev/null @@ -1,245 +0,0 @@ -ListCtrlPrinter CalculateTotalPages 1862.3 ms -Tue Aug 05 10:50:53 2008 app.prof - - 267006 function calls in 1.742 CPU seconds - - Ordered by: internal time, call count - List reduced from 209 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 12584 0.211 0.000 0.291 0.000 _controls.py:4521(GetItem) - 18920 0.084 0.000 0.120 0.000 _controls.py:4178() - 6336 0.082 0.000 0.122 0.000 _controls.py:4491(GetColumn) - 3167 0.079 0.000 0.495 0.000 ListCtrlPrinter.py:1140(DrawText) - 305 0.078 0.000 1.064 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 3167 0.066 0.000 0.066 0.000 _gdi.py:3585(DrawLabel) - 572 0.061 0.000 0.245 0.000 ListCtrlPrinter.py:2027(GetTexts) - 572 0.061 0.000 0.237 0.000 ListCtrlPrinter.py:2039(GetImages) - 572 0.060 0.000 0.212 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 610 0.051 0.000 0.885 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 3163 0.049 0.000 0.260 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 3209 0.045 0.000 0.048 0.000 _gdi.py:3791(GetPartialTextExtents) - 18920 0.036 0.000 0.036 0.000 {method 'own' of 'PySwigObject' objects} - 18920 0.036 0.000 0.036 0.000 _controls.py:4184() - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6288 0.033 0.000 0.102 0.000 ListCtrlPrinter.py:962(GetFormat) - 3163 0.030 0.000 0.098 0.000 WordWrapRenderer.py:113(_Truncate) - 6324 0.030 0.000 0.069 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.027 0.000 0.042 0.000 ListCtrlPrinter.py:2304(__init__) - 6324 0.025 0.000 0.039 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 6314 0.025 0.000 0.025 0.000 _controls.py:4261(GetText) - 3080 0.023 0.000 0.079 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 574 0.021 0.000 0.058 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 3168 0.018 0.000 0.025 0.000 _core.py:1133(__init__) - 6314 0.018 0.000 0.018 0.000 _controls.py:4265(GetImage) - 6292 0.018 0.000 0.018 0.000 _controls.py:4277(GetAlign) - 3179 0.016 0.000 0.016 0.000 _gdi.py:4022(SetTextForeground) - 6422 0.015 0.000 0.015 0.000 {method 'update' of 'dict' objects} - 3167 0.014 0.000 0.022 0.000 _core.py:1440(__getitem__) - 6324 0.014 0.000 0.014 0.000 {getattr} - -ListCtrlPrinter CalculateTotalPages 1770.4 ms -Tue Aug 05 11:11:21 2008 app.prof - - 266950 function calls in 1.722 CPU seconds - - Ordered by: internal time, call count - List reduced from 209 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 12584 0.208 0.000 0.288 0.000 _controls.py:4521(GetItem) - 3167 0.083 0.000 0.489 0.000 ListCtrlPrinter.py:1140(DrawText) - 18920 0.083 0.000 0.119 0.000 _controls.py:4178() - 6336 0.081 0.000 0.121 0.000 _controls.py:4491(GetColumn) - 305 0.078 0.000 1.058 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 3167 0.065 0.000 0.065 0.000 _gdi.py:3585(DrawLabel) - 572 0.061 0.000 0.236 0.000 ListCtrlPrinter.py:2041(GetImages) - 572 0.061 0.000 0.238 0.000 ListCtrlPrinter.py:2027(GetTexts) - 572 0.059 0.000 0.209 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 610 0.050 0.000 0.874 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 3163 0.046 0.000 0.251 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 3213 0.038 0.000 0.040 0.000 _gdi.py:3791(GetPartialTextExtents) - 18920 0.036 0.000 0.036 0.000 {method 'own' of 'PySwigObject' objects} - 18920 0.036 0.000 0.036 0.000 _controls.py:4184() - 6325 0.035 0.000 0.035 0.000 _controls.py:4501(GetColumnWidth) - 6288 0.034 0.000 0.100 0.000 ListCtrlPrinter.py:962(GetFormat) - 3163 0.030 0.000 0.092 0.000 WordWrapRenderer.py:113(_Truncate) - 6324 0.028 0.000 0.067 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.027 0.000 0.042 0.000 ListCtrlPrinter.py:2308(__init__) - 6324 0.025 0.000 0.039 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 3080 0.022 0.000 0.077 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 574 0.021 0.000 0.057 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 6314 0.019 0.000 0.019 0.000 _controls.py:4261(GetText) - 6314 0.019 0.000 0.019 0.000 _controls.py:4265(GetImage) - 3168 0.019 0.000 0.025 0.000 _core.py:1133(__init__) - 6292 0.017 0.000 0.017 0.000 _controls.py:4277(GetAlign) - 3179 0.016 0.000 0.016 0.000 _gdi.py:4022(SetTextForeground) - 6422 0.014 0.000 0.014 0.000 {method 'update' of 'dict' objects} - 3167 0.014 0.000 0.022 0.000 _core.py:1440(__getitem__) - 6324 0.014 0.000 0.014 0.000 {getattr} - - - -ListCtrlPrinter CalculateTotalPages 1855.8 ms -Tue Aug 05 10:56:46 2008 app.prof - - 294965 function calls in 1.805 CPU seconds - - Ordered by: internal time, call count - List reduced from 232 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 6336 0.084 0.000 0.126 0.000 _controls.py:4491(GetColumn) - 3167 0.079 0.000 0.560 0.000 ListCtrlPrinter.py:1140(DrawText) - 3167 0.078 0.000 0.097 0.000 _gdi.py:3585(DrawLabel) - 305 0.073 0.000 1.132 0.004 ListCtrlPrinter.py:1411(DrawSelf) - 6292 0.061 0.000 0.134 0.000 ObjectListView.py:929(GetImageAt) - 3224 0.060 0.000 0.083 0.000 _gdi.py:3791(GetPartialTextExtents) - 572 0.060 0.000 0.215 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 7436 0.059 0.000 0.081 0.000 ObjectListView.py:3420(_Munge) - 610 0.051 0.000 0.879 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 6292 0.050 0.000 0.246 0.000 ObjectListView.py:3274(GetStringValue) - 3163 0.048 0.000 0.324 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 6292 0.045 0.000 0.096 0.000 ObjectListView.py:3282(_StringToValue) - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6292 0.035 0.000 0.100 0.000 ObjectListView.py:3267(GetValue) - 6288 0.033 0.000 0.106 0.000 ListCtrlPrinter.py:962(GetFormat) - 6324 0.032 0.000 0.046 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 6292 0.030 0.000 0.276 0.000 ObjectListView.py:1028(GetStringValueAt) - 3163 0.030 0.000 0.131 0.000 WordWrapRenderer.py:113(_Truncate) - 13760 0.029 0.000 0.029 0.000 {getattr} - 6924 0.029 0.000 0.051 0.000 cp1252.py:14(decode) - 6336 0.029 0.000 0.042 0.000 _controls.py:4178() - 6324 0.027 0.000 0.073 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.026 0.000 0.041 0.000 ListCtrlPrinter.py:2308(__init__) - 572 0.024 0.000 0.309 0.001 ListCtrlPrinter.py:2027(GetTexts) - 1188 0.023 0.000 0.023 0.000 {method 'strftime' of 'datetime.date' objects} - 3080 0.023 0.000 0.078 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 572 0.022 0.000 0.165 0.000 ListCtrlPrinter.py:2041(GetImages) - 6292 0.021 0.000 0.041 0.000 ObjectListView.py:3349(GetImage) - 6924 0.021 0.000 0.021 0.000 {_codecs.charmap_decode} - 574 0.021 0.000 0.058 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - -ListCtrlPrinter CalculateTotalPages 1700.5 ms -Tue Aug 05 11:14:24 2008 app.prof - - 275373 function calls in 1.665 CPU seconds - - Ordered by: internal time, call count - List reduced from 231 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 3167 0.078 0.000 0.564 0.000 ListCtrlPrinter.py:1140(DrawText) - 3167 0.076 0.000 0.095 0.000 _gdi.py:3585(DrawLabel) - 305 0.075 0.000 1.064 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 7436 0.061 0.000 0.083 0.000 ObjectListView.py:3420(_Munge) - 6292 0.060 0.000 0.135 0.000 ObjectListView.py:929(GetImageAt) - 3209 0.055 0.000 0.084 0.000 _gdi.py:3791(GetPartialTextExtents) - 610 0.052 0.000 0.743 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 6292 0.051 0.000 0.253 0.000 ObjectListView.py:3274(GetStringValue) - 3163 0.049 0.000 0.324 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 6292 0.045 0.000 0.099 0.000 ObjectListView.py:3282(_StringToValue) - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6292 0.036 0.000 0.102 0.000 ObjectListView.py:3267(GetValue) - 6905 0.035 0.000 0.056 0.000 cp1252.py:14(decode) - 6292 0.034 0.000 0.045 0.000 ObjectListView.py:3241(GetAlignment) - 6288 0.033 0.000 0.102 0.000 ListCtrlPrinter.py:962(GetFormat) - 6292 0.030 0.000 0.283 0.000 ObjectListView.py:1028(GetStringValueAt) - 3163 0.030 0.000 0.132 0.000 WordWrapRenderer.py:113(_Truncate) - 13760 0.029 0.000 0.029 0.000 {getattr} - 6324 0.029 0.000 0.069 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.026 0.000 0.041 0.000 ListCtrlPrinter.py:2309(__init__) - 6324 0.026 0.000 0.040 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 572 0.025 0.000 0.316 0.001 ListCtrlPrinter.py:2027(GetTexts) - 3080 0.023 0.000 0.081 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 572 0.023 0.000 0.166 0.000 ListCtrlPrinter.py:2042(GetImages) - 572 0.023 0.000 0.070 0.000 ListCtrlPrinter.py:2035(GetAlignments) - 574 0.022 0.000 0.060 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 1188 0.022 0.000 0.022 0.000 {method 'strftime' of 'datetime.date' objects} - 6292 0.021 0.000 0.042 0.000 ObjectListView.py:3349(GetImage) - 6905 0.021 0.000 0.021 0.000 {_codecs.charmap_decode} - 9724 0.020 0.000 0.020 0.000 {isinstance} - -#====================================================================== - -FASTLIST 10,000 rows - -ListCtrlPrinter CalculateTotalPages 142273.9 ms -Tue Aug 05 11:23:54 2008 app.prof - - 22351842 function calls in 142.228 CPU seconds - - Ordered by: internal time, call count - List reduced from 214 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 507298 19.119 0.000 70.122 0.000 _controls.py:4521(GetItem) - 507298 5.051 0.000 11.103 0.000 ObjectListView.py:929(GetImageAt) - 599534 5.027 0.000 6.828 0.000 ObjectListView.py:3420(_Munge) - 507298 4.048 0.000 20.396 0.000 ObjectListView.py:3274(GetStringValue) - 507298 3.718 0.000 7.838 0.000 ObjectListView.py:3282(_StringToValue) - 507298 3.444 0.000 28.113 0.000 ObjectListView.py:2116(OnGetItemText) - 760991 3.437 0.000 4.925 0.000 _controls.py:4178() - 253693 3.416 0.000 5.045 0.000 _controls.py:4491(GetColumn) - 507298 3.400 0.000 16.120 0.000 ObjectListView.py:2130(OnGetItemColumnImage) - 1014596 3.338 0.000 3.396 0.000 ObjectListView.py:2155(GetObjectAt) - 25042 3.274 0.000 75.245 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 131137 3.253 0.000 21.029 0.000 ListCtrlPrinter.py:1140(DrawText) - 507298 2.930 0.000 8.510 0.000 ObjectListView.py:3267(GetValue) - 50082 2.834 0.000 97.934 0.002 ListCtrlPrinter.py:1269(GetCombinedLists) - 46118 2.810 0.000 39.491 0.001 ListCtrlPrinter.py:2027(GetTexts) - 46118 2.783 0.000 38.978 0.001 ListCtrlPrinter.py:2039(GetImages) - 46118 2.677 0.000 9.037 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 131137 2.601 0.000 2.601 0.000 _gdi.py:3585(DrawLabel) - 507298 2.494 0.000 22.890 0.000 ObjectListView.py:1028(GetStringValueAt) - 533378 2.348 0.000 4.062 0.000 cp1252.py:14(decode) - 990365 2.132 0.000 2.132 0.000 {getattr} - 386871 2.069 0.000 6.193 0.000 ListCtrlPrinter.py:962(GetFormat) - 131131 1.995 0.000 10.308 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 96209 1.921 0.000 1.921 0.000 {method 'strftime' of 'datetime.date' objects} - 507298 1.777 0.000 3.327 0.000 ObjectListView.py:3349(GetImage) - 144064 1.770 0.000 1.971 0.000 _gdi.py:3791(GetPartialTextExtents) - 157504 1.752 0.000 1.752 0.000 _gdi.py:3659(SetFont) - 390831 1.744 0.000 4.167 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 784006 1.722 0.000 1.722 0.000 {isinstance} - 533378 1.714 0.000 1.714 0.000 {_codecs.charmap_decode} - -ListCtrlPrinter CalculateTotalPages 79258.0 ms -Tue Aug 05 11:26:44 2008 app.prof - - 13485917 function calls in 79.211 CPU seconds - - Ordered by: internal time, call count - List reduced from 210 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 25042 3.295 0.000 45.882 0.002 ListCtrlPrinter.py:1411(DrawSelf) - 131137 3.253 0.000 23.338 0.000 ListCtrlPrinter.py:1140(DrawText) - 131137 2.955 0.000 3.753 0.000 _gdi.py:3585(DrawLabel) - 50082 2.806 0.000 32.646 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 253649 2.452 0.000 5.388 0.000 ObjectListView.py:929(GetImageAt) - 299767 2.376 0.000 3.247 0.000 ObjectListView.py:3420(_Munge) - 144264 2.206 0.000 3.250 0.000 _gdi.py:3791(GetPartialTextExtents) - 386871 2.045 0.000 6.129 0.000 ListCtrlPrinter.py:962(GetFormat) - 131131 2.019 0.000 12.593 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 253649 1.997 0.000 9.879 0.000 ObjectListView.py:3274(GetStringValue) - 253649 1.808 0.000 3.832 0.000 ObjectListView.py:3282(_StringToValue) - 157504 1.788 0.000 1.788 0.000 _gdi.py:3659(SetFont) - 390831 1.705 0.000 4.125 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 390831 1.572 0.000 2.420 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 253682 1.489 0.000 1.489 0.000 _controls.py:4501(GetColumnWidth) - 690598 1.465 0.000 1.465 0.000 {getattr} - 253649 1.421 0.000 4.050 0.000 ObjectListView.py:3267(GetValue) - 253649 1.390 0.000 1.861 0.000 ObjectListView.py:3241(GetAlignment) - 300016 1.305 0.000 2.212 0.000 cp1252.py:14(decode) - 253649 1.242 0.000 11.121 0.000 ObjectListView.py:1028(GetStringValueAt) - 131131 1.201 0.000 4.871 0.000 WordWrapRenderer.py:113(_Truncate) - 46118 1.195 0.000 12.728 0.000 ListCtrlPrinter.py:2045(GetTexts) - 265551 1.117 0.000 1.741 0.000 ListCtrlPrinter.py:2324(__init__) - 46118 1.091 0.000 6.745 0.000 ListCtrlPrinter.py:2058(GetImages) - 46118 1.082 0.000 3.062 0.000 ListCtrlPrinter.py:2052(GetAlignments) - 101484 1.060 0.000 1.683 0.000 ListCtrlPrinter.py:1355(_CalculateCellPadding) - 46122 0.998 0.000 2.608 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 123200 0.936 0.000 3.169 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 50091 0.930 0.000 0.930 0.000 {method 'strftime' of 'datetime.date' objects} - 300016 0.907 0.000 0.907 0.000 {_codecs.charmap_decode} From a8473181b7a48b89c0194953f7ab709d8f183df7 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 12 Nov 2016 15:01:51 -0700 Subject: [PATCH 075/158] Updated the template that can be downloaded from AddPointForm --- odmtools/controller/frmAddPoints.py | 25 ++++--------- odmtools/controller/frmBulkInsert.py | 36 +++++++++---------- odmtools/controller/logicPlotOptions.py | 2 +- odmtools/gui/frmConsole.py | 2 +- odmtools/gui/frmODMTools.py | 6 ++-- odmtools/gui/plotSummary.py | 4 +-- .../lib/ObjectListView/ListCtrlPrinter.py | 4 +-- .../lib/ObjectListView/WordWrapRenderer.py | 2 +- .../virtualObjectListviewExample.py | 2 +- odmtools/lib/oldOlv/ListCtrlPrinter.py | 4 +-- odmtools/lib/oldOlv/WordWrapRenderer.py | 2 +- odmtools/view/clsAddPoints.py | 2 +- odmtools/view/clsBulkInsert.py | 10 +++--- profiler/runsnake.py | 2 +- tests/test_controller/test_bulkInsert.py | 6 ++-- tests/test_controller/test_frmAddPoints.py | 2 +- tests/test_controller/test_frmDBConfig.py | 2 +- tests/test_gui/test_wizSave.py | 2 +- 18 files changed, 50 insertions(+), 65 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index b3ce315..8d477fc 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -1,7 +1,7 @@ """Subclass of AddPoints, which is generated by wxFormBuilder.""" import datetime import wx -from odmtools.controller.frmBulkInsert import BulkInsert +from odmtools.controller.frmBulkInsert import BulkInsertController import odmtools.view.clsAddPoints as clsAddPoints try: from agw import genericmessagedialog as GMD @@ -19,7 +19,7 @@ def __init__(self, parent, **kwargs): if 'recordService' in kwargs: self.recordService = kwargs['recordService'] clsAddPoints.AddPoints.__init__(self, parent, **kwargs) - self.frame = BulkInsert(self) + self.bulkInsertCtrl = BulkInsertController(self) def checkIfEditing(self): # Deleting a cell being edited doesn't finish editing @@ -97,12 +97,6 @@ def onDeleteBtn(self, event): event.Skip() def customRemove(self, object): - """ - - - :param object: - :return: - """ obj = self.olv.GetObjects() if isinstance(object, list): for x in object: @@ -112,18 +106,13 @@ def customRemove(self, object): self.olv.SetObjects(obj) def onUploadBtn(self, event): - """ - - :param event: - :return: - """ self.checkIfEditing() - if not self.frame.IsShown(): - self.frame.CenterOnParent() - self.frame.ShowModal() - self.frame.SetFocus() + if not self.bulkInsertCtrl.IsShown(): + self.bulkInsertCtrl.CenterOnParent() + self.bulkInsertCtrl.ShowModal() + self.bulkInsertCtrl.SetFocus() else: - self.frame.Hide() + self.bulkInsertCtrl.Hide() event.Skip() diff --git a/odmtools/controller/frmBulkInsert.py b/odmtools/controller/frmBulkInsert.py index b480145..4d5bfe6 100644 --- a/odmtools/controller/frmBulkInsert.py +++ b/odmtools/controller/frmBulkInsert.py @@ -13,13 +13,12 @@ __author__ = 'Jacob' -class BulkInsert(clsBulkInsert.BulkInsert): +class BulkInsertController(clsBulkInsert.BulkInsertView): def __init__(self, parent): - clsBulkInsert.BulkInsert.__init__(self, parent) + clsBulkInsert.BulkInsertView.__init__(self, parent) self.parent = parent - self.col = ['DataValue', 'Date', 'Time', 'UTCOffSet', 'CensorCode', 'ValueAccuracy', 'OffSetValue', - 'OffSetType', 'QualifierCode', 'LabSampleCode'] + self.columns = ["DataValue", "Date", "Time", "UTFOffset", "CensorCode", "QualityCode", "TimeAggregationInterval", "TimeAggregationUnitID", "Annotation"] def obtainFilePath(self): ## Obtain CSV filepath @@ -61,8 +60,7 @@ def readDataFromCSV(self, filepath): 5: str.strip, 6: str.strip, 7: str.strip, - 8: str.strip, - 9: str.strip}) + 8: str.strip}) except CParserError as e: message = "There was an issue trying to parse your file. "\ "Please compare your csv with the template version as the file"\ @@ -112,6 +110,7 @@ def loadIntoDataFrame(self, data): dlg.Destroy() return pointList + def onUpload(self, event): """Reads csv into pandas object @@ -143,7 +142,7 @@ def onUpload(self, event): self.parent.Raise() event.Skip() - def onTemplate(self, event): + def onDownloadTemplateButton(self, event): """ DataValues: Floats or -9999 (No data value) Date: --+ String @@ -160,28 +159,25 @@ def onTemplate(self, event): :param event: :return: """ - saveFileDialog = wx.FileDialog(self, "Save Bulk Insert Template", "", "", "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - value = saveFileDialog.ShowModal() + + file_dialog = wx.FileDialog(self, "Save Bulk Insert Template", "", "", "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + value = file_dialog.ShowModal() if value == wx.ID_CANCEL: return - filepath = saveFileDialog.GetPath() - df = pd.DataFrame(columns=self.col) + filepath = file_dialog.GetPath() + df = pd.DataFrame(columns=self.columns) df.loc[0] = ['FLOAT|INT', 'YYYY-MM-DD', 'HH:MM:SS', 'INT', 'gt|nc|lt|nd|pnq', 'FLOAT', 'FLOAT', - 'String', 'String', 'String'] - df.loc[1] = ['-9999', '2005-06-29', '14:20:15', '-7', 'nc', "1.2", "1", "NULL", "NULL", "NULL"] + 'String', 'String'] + df.loc[1] = ['-9999', '2005-06-29', '14:20:15', '-7', 'nc', "1.2", "1", "NULL", "NULL"] df.to_csv(filepath, index=False) - - self.EndModal(0) # Denver - #self.Hide() - self.parent.Raise() + self.onClose(None) def onClose(self, event): - self.EndModal(0) # Denver - #self.Hide() + self.EndModal(0) self.parent.Raise() if __name__ == '__main__': app = wx.App(useBestVisual=True) - m = BulkInsert(None) + m = BulkInsertController(None) m.Show() app.MainLoop() diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 31d3130..ceeb6ef 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -537,7 +537,7 @@ def __init__(self, data): #PrbExc = ranks/(length(sorted)+1)*100 #Here I plot the probability of exceedance (PrbExc) against the sorted initial values (sorted). - #plot(PrbExc, sorted, type='n', col='white', font.lab=1.5, xlab="Frequency of Exceedance, percent", ylab="TSS, mg/L",log="y") + #plot(PrbExc, sorted, type='n', columns='white', font.lab=1.5, xlab="Frequency of Exceedance, percent", ylab="TSS, mg/L",log="y") :param data: diff --git a/odmtools/gui/frmConsole.py b/odmtools/gui/frmConsole.py index 5a112d0..1cf3c12 100644 --- a/odmtools/gui/frmConsole.py +++ b/odmtools/gui/frmConsole.py @@ -8,7 +8,7 @@ __author__ = 'Jacob' class ModifiedFrame(Frame): - """Override standard PyCrust frame in order to remove exit and about page""" + """Override standard PyCrust bulkInsertCtrl in order to remove exit and about page""" def __init__(self, *args, **kwargs): Frame.__init__(self, *args, **kwargs) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index 0d936b2..b208b41 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -210,7 +210,7 @@ def on_about_request(self, event): def MacReopenApp(self): """Called when the doc icon is clicked, and ???""" - try: # it's possible for this event to come when the frame is closed + try: # it's possible for this event to come when the bulkInsertCtrl is closed self.GetTopWindow().Raise() except: pass @@ -219,7 +219,7 @@ def MacReopenApp(self): def _init_ctrls(self, series_service): # generated method, don't edit - logger.debug("Loading frame...") + logger.debug("Loading bulkInsertCtrl...") self.SetIcon(gtk_execute.getIcon()) @@ -504,7 +504,7 @@ def onClose(self, event): if value == wx.ID_YES: self.txtPythonScript.OnSaveAs(event) - # deinitialize the frame manager + # deinitialize the bulkInsertCtrl manager self.pnlPlot.Close() try: f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'w') diff --git a/odmtools/gui/plotSummary.py b/odmtools/gui/plotSummary.py index 095f342..12e07fb 100644 --- a/odmtools/gui/plotSummary.py +++ b/odmtools/gui/plotSummary.py @@ -110,7 +110,7 @@ def resizeLabel(self): def clear(self): self.btnExport.Enable(False) if self.grdSummary.GetNumberCols() > 0: - # for col in range(self.grdSummary.GetNumberCols()) + # for columns in range(self.grdSummary.GetNumberCols()) self.grdSummary.DeleteCols(pos=0, numCols=self.grdSummary.GetNumberCols(), updateLabels=True) @@ -149,7 +149,7 @@ def addCol(self, series): self.grdSummary.AppendCols(numCols=1, updateLabels=True) col = self.grdSummary.GetNumberCols() - 1 self.setColLabel(col, series.siteName + "- " + series.variableName) - #self.grdSummary.AutoSizeColLabelSize(col) + #self.grdSummary.AutoSizeColLabelSize(columns) stats = series.Statistics count = stats.NumberofObservations diff --git a/odmtools/lib/ObjectListView/ListCtrlPrinter.py b/odmtools/lib/ObjectListView/ListCtrlPrinter.py index e1b3fe5..35d8164 100644 --- a/odmtools/lib/ObjectListView/ListCtrlPrinter.py +++ b/odmtools/lib/ObjectListView/ListCtrlPrinter.py @@ -1658,7 +1658,7 @@ def DrawSelf(self, dc, bounds): right = RectUtils.Right(x.cell) dc.DrawLine(right, top, right, bottom) - # Draw the surrounding frame + # Draw the surrounding bulkInsertCtrl left = RectUtils.Left(combined[0].cell) right = RectUtils.Right(combined[-1].cell) dc.DrawRectangle(left, top, right-left, bottom-top) @@ -2369,7 +2369,7 @@ class RectangleDecoration(Decoration): A RectangleDecoration draw a rectangle around or on the side of a block. The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. + a bulkInsertCtrl drawn as well. """ diff --git a/odmtools/lib/ObjectListView/WordWrapRenderer.py b/odmtools/lib/ObjectListView/WordWrapRenderer.py index c4dd1c9..b27cabf 100644 --- a/odmtools/lib/ObjectListView/WordWrapRenderer.py +++ b/odmtools/lib/ObjectListView/WordWrapRenderer.py @@ -184,7 +184,7 @@ def OnPaint(self, evt): inset = (20, 20, 20, 20) rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - # Calculate exactly how high the wrapped is going to be and put a frame around it. + # Calculate exactly how high the wrapped is going to be and put a bulkInsertCtrl around it. dc.SetFont(self.font) dc.SetPen(wx.RED_PEN) rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) diff --git a/odmtools/lib/ObjectListView/virtualObjectListviewExample.py b/odmtools/lib/ObjectListView/virtualObjectListviewExample.py index bebdb64..78d1433 100644 --- a/odmtools/lib/ObjectListView/virtualObjectListviewExample.py +++ b/odmtools/lib/ObjectListView/virtualObjectListviewExample.py @@ -183,7 +183,7 @@ def GetOLVColClicked(self, event): # each column consecutively until found. if left_pxl_col <= x <= right_pxl_col: - # Mouse was clicked in the current column "col"; done + # Mouse was clicked in the current column "columns"; done col_selected = col break diff --git a/odmtools/lib/oldOlv/ListCtrlPrinter.py b/odmtools/lib/oldOlv/ListCtrlPrinter.py index e1b3fe5..35d8164 100644 --- a/odmtools/lib/oldOlv/ListCtrlPrinter.py +++ b/odmtools/lib/oldOlv/ListCtrlPrinter.py @@ -1658,7 +1658,7 @@ def DrawSelf(self, dc, bounds): right = RectUtils.Right(x.cell) dc.DrawLine(right, top, right, bottom) - # Draw the surrounding frame + # Draw the surrounding bulkInsertCtrl left = RectUtils.Left(combined[0].cell) right = RectUtils.Right(combined[-1].cell) dc.DrawRectangle(left, top, right-left, bottom-top) @@ -2369,7 +2369,7 @@ class RectangleDecoration(Decoration): A RectangleDecoration draw a rectangle around or on the side of a block. The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. + a bulkInsertCtrl drawn as well. """ diff --git a/odmtools/lib/oldOlv/WordWrapRenderer.py b/odmtools/lib/oldOlv/WordWrapRenderer.py index c4dd1c9..b27cabf 100644 --- a/odmtools/lib/oldOlv/WordWrapRenderer.py +++ b/odmtools/lib/oldOlv/WordWrapRenderer.py @@ -184,7 +184,7 @@ def OnPaint(self, evt): inset = (20, 20, 20, 20) rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - # Calculate exactly how high the wrapped is going to be and put a frame around it. + # Calculate exactly how high the wrapped is going to be and put a bulkInsertCtrl around it. dc.SetFont(self.font) dc.SetPen(wx.RED_PEN) rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) diff --git a/odmtools/view/clsAddPoints.py b/odmtools/view/clsAddPoints.py index 3ecf4e8..89aff19 100644 --- a/odmtools/view/clsAddPoints.py +++ b/odmtools/view/clsAddPoints.py @@ -144,7 +144,7 @@ def onEditFinish(self, event): #print "Finished Editing Cell!", event.subItemIndex def onColClick(self, event): - ## Ignore col clicking + ## Ignore columns clicking pass def __del__(self): diff --git a/odmtools/view/clsBulkInsert.py b/odmtools/view/clsBulkInsert.py index 2b9a033..a605070 100644 --- a/odmtools/view/clsBulkInsert.py +++ b/odmtools/view/clsBulkInsert.py @@ -22,7 +22,7 @@ ## Class BulkInsert ########################################################################### -class BulkInsert(wx.Dialog): +class BulkInsertView(wx.Dialog): def __init__(self, parent): wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="- ODMTools Bulk Insert -", size=(400, 600)) @@ -30,12 +30,12 @@ def __init__(self, parent): self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) self.uploadBtn = GB.GradientButton(mainPanel, wx.ID_ANY, upload_2_32.GetBitmap(), ' Upload') - self.templateBtn = GB.GradientButton(mainPanel, wx.ID_ANY, downloading_updates_32.GetBitmap(), ' Download') + self.downloadTemplateButton = GB.GradientButton(mainPanel, wx.ID_ANY, downloading_updates_32.GetBitmap(), ' Download') self.closeBtn = GB.GradientButton(mainPanel, wx.ID_ANY, close_window_32.GetBitmap(), " Close") self._initSizers(mainPanel) self.Bind(wx.EVT_BUTTON, self.onUpload, self.uploadBtn) - self.Bind(wx.EVT_BUTTON, self.onTemplate, self.templateBtn) + self.Bind(wx.EVT_BUTTON, self.onDownloadTemplateButton, self.downloadTemplateButton) self.Bind(wx.EVT_BUTTON, self.onClose, self.closeBtn) def _initSizers(self, mainPanel): @@ -54,7 +54,7 @@ def _initSizers(self, mainPanel): sbTemplateSizer = wx.StaticBoxSizer(wx.StaticBox(mainPanel, wx.ID_ANY, u"Download CSV Template"), wx.VERTICAL) fgTemplateSizer = wx.FlexGridSizer(0, 2, 0, 0) fgTemplateSizer.SetFlexibleDirection(wx.VERTICAL) - fgTemplateSizer.Add(self.templateBtn, 0, wx.ALL, 5) + fgTemplateSizer.Add(self.downloadTemplateButton, 0, wx.ALL, 5) sbTemplateSizer.Add(fgTemplateSizer, 1, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5) @@ -78,7 +78,7 @@ def __del__(self): ## Virtual event handlers, overridden in inherited class def onUpload(self, event): event.Skip() - def onTemplate(self, event): + def onDownloadTemplateButton(self, event): event.Skip() def onClose(self, event): event.Skip() diff --git a/profiler/runsnake.py b/profiler/runsnake.py index d01b4ac..49704c3 100644 --- a/profiler/runsnake.py +++ b/profiler/runsnake.py @@ -186,7 +186,7 @@ def mem_name( x ): class MainFrame(wx.Frame): - """The root frame for the display of a single data-set""" + """The root bulkInsertCtrl for the display of a single data-set""" loader = None percentageView = False diff --git a/tests/test_controller/test_bulkInsert.py b/tests/test_controller/test_bulkInsert.py index e881d6c..cdcfcbd 100644 --- a/tests/test_controller/test_bulkInsert.py +++ b/tests/test_controller/test_bulkInsert.py @@ -1,11 +1,11 @@ import wx import os -from odmtools.controller.frmBulkInsert import BulkInsert +from odmtools.controller.frmBulkInsert import BulkInsertController class TestBulkInsert: def setup(self): self.app = wx.App() - self.BulkInsert = BulkInsert(None) + self.BulkInsert = BulkInsertController(None) path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) self.CSVPath = os.path.join(path, 'example_files', 'exampleBulkInsert.csv') self.CSVPath2 =os.path.join(path, 'example_files', 'exampleBulkInsert_win.csv') @@ -39,7 +39,7 @@ def test_loadIntoDataFrame(self): def test_onUpload(self): assert self.BulkInsert - assert isinstance(self.BulkInsert.col, list) + assert isinstance(self.BulkInsert.columns, list) assert self.CSVPath diff --git a/tests/test_controller/test_frmAddPoints.py b/tests/test_controller/test_frmAddPoints.py index 777efdb..7acdca5 100755 --- a/tests/test_controller/test_frmAddPoints.py +++ b/tests/test_controller/test_frmAddPoints.py @@ -50,7 +50,7 @@ def FireEvent(self): ''' evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId, self.frame.addRowBtn.GetId()) - #wx.PostEvent(self.frame.addRowBtn, evt) + #wx.PostEvent(self.bulkInsertCtrl.addRowBtn, evt) self.frame.GetEventHandler().ProcessEvent(evt) assert self.olv.GetObjects() diff --git a/tests/test_controller/test_frmDBConfig.py b/tests/test_controller/test_frmDBConfig.py index a5c69d4..e0bc7da 100755 --- a/tests/test_controller/test_frmDBConfig.py +++ b/tests/test_controller/test_frmDBConfig.py @@ -19,7 +19,7 @@ def setup(self): ''' def test_form(self): """test form""" - assert self.frame.panel.choices == { + assert self.bulkInsertCtrl.panel.choices == { "Microsoft SQL Server": 'mssql', "MySQL": 'mysql', "PostgreSQL":"postgresql" } ''' diff --git a/tests/test_gui/test_wizSave.py b/tests/test_gui/test_wizSave.py index 0685d6b..c5bc834 100644 --- a/tests/test_gui/test_wizSave.py +++ b/tests/test_gui/test_wizSave.py @@ -39,7 +39,7 @@ def setup(self): self.wizard = wizSave(self.frame,self.sm, self.sm.get_edit_service(self.series.id, self.memory_database)) #TODO get wizard tests working # def test___init__(self): - # assert self.frame + # assert self.bulkInsertCtrl # assert self.wizard # self.wizard.init(self, self.sm, self.memory_database) # From 07f3176987f0c5030ba0ac809d1e3ac5c578af42 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 15 Nov 2016 12:39:03 -0700 Subject: [PATCH 076/158] Export/downloading and uploading work Need to update the validation --- ...mBulkInsert.py => BulkInsertController.py} | 15 ++++------- odmtools/controller/frmAddPoints.py | 2 +- odmtools/controller/olvAddPoint.py | 26 +++++++++---------- .../{clsBulkInsert.py => BulkInsertView.py} | 0 tests/test_controller/test_bulkInsert.py | 2 +- 5 files changed, 19 insertions(+), 26 deletions(-) rename odmtools/controller/{frmBulkInsert.py => BulkInsertController.py} (92%) rename odmtools/view/{clsBulkInsert.py => BulkInsertView.py} (100%) diff --git a/odmtools/controller/frmBulkInsert.py b/odmtools/controller/BulkInsertController.py similarity index 92% rename from odmtools/controller/frmBulkInsert.py rename to odmtools/controller/BulkInsertController.py index 4d5bfe6..ebb8ab5 100644 --- a/odmtools/controller/frmBulkInsert.py +++ b/odmtools/controller/BulkInsertController.py @@ -2,7 +2,7 @@ Bulk Insert of points """ import wx -import odmtools.view.clsBulkInsert as clsBulkInsert +import odmtools.view.BulkInsertView as clsBulkInsert import odmtools.controller.olvAddPoint as olv import pandas as pd from pandas.parser import CParserError @@ -52,15 +52,10 @@ def readDataFromCSV(self, filepath): try: #data = pd.read_csv(filepath, skiprows=[1], engine='c', lineterminator='\n') - data = pd.read_csv(csv_data, skiprows=[1], engine='c', converters={0: str.strip, - 1: str.strip, - 2: str.strip, - 3: str.strip, - 4: str.strip, - 5: str.strip, - 6: str.strip, - 7: str.strip, - 8: str.strip}) + data = pd.read_csv(csv_data, skiprows=[1], engine='c', converters={ + 0: str.strip, 1: str.strip, 2: str.strip, 3: str.strip, + 4: str.strip, 5: str.strip, 6: str.strip, 7: str.strip, + 8: str.strip}) except CParserError as e: message = "There was an issue trying to parse your file. "\ "Please compare your csv with the template version as the file"\ diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index 8d477fc..c9c6a44 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -1,7 +1,7 @@ """Subclass of AddPoints, which is generated by wxFormBuilder.""" import datetime import wx -from odmtools.controller.frmBulkInsert import BulkInsertController +from odmtools.controller.BulkInsertController import BulkInsertController import odmtools.view.clsAddPoints as clsAddPoints try: from agw import genericmessagedialog as GMD diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 7eb953f..6d3bb4d 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -9,12 +9,12 @@ class Points(object): - def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, - censorCode="NULL", offSetValue="NULL"): + def __init__(self, data_value="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, + censor_code="NULL", quality_code="NULL", time_agg_interval="NULL", time_agg_unit="NULL", annotation="NULL"): try: - self.dataValue = str(dataValue) + self.dataValue = str(data_value) except: - self.dataValue = dataValue + self.dataValue = data_value try: self.time = str(time) except: @@ -24,12 +24,11 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.valueDateTime = self.date self.utcOffSet = str(utcOffSet) - self.offSetValue = offSetValue - self.censorCode = censorCode - self.qualityCodeCV = "NULL" - self.timeAggInterval = -1 - self.timeAggregationUnitID = "NULL" - self.annotation = "NULL" + self.censorCode = censor_code + self.qualityCodeCV = quality_code + self.timeAggInterval = time_agg_interval + self.timeAggregationUnitID = time_agg_unit + self.annotation = annotation ## determines whether a row is in correct format or now self.validDataValue = False @@ -38,7 +37,6 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.validUTCOffSet = False self.validCensorCode = False self.validValueAcc = False - self.validOffSetValue = False self.validOffSetType = False @@ -69,7 +67,7 @@ def __init__(self, *args, **kwargs): self.imgGetterTime = cellEdit.imgGetterTime self.imgGetterCensorCode = cellEdit.imgGetterCensorCode self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset - self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue + # self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue ## Custom Value Setters ## Sets the value, can modify rules for setting value @@ -81,7 +79,7 @@ def __init__(self, *args, **kwargs): self.localtime2Str = cellEdit.strConverterLocalTime self.str2DataValue = cellEdit.strConverterDataValue self.utcOffSet2Str = cellEdit.strConverterUTCOffset - self.offSetValue2Str = cellEdit.strConverterOffSetValue + # self.offSetValue2Str = cellEdit.strConverterOffSetValue ## Custom CellEditors ## Custom cell editors for each cell @@ -160,7 +158,7 @@ def rowFormatter(listItem, point): def isCorrect(self, point): validators = [ self.imgGetterDataValue, self.imgGetterDate, self.imgGetterTime, self.imgGetterCensorCode, - self.imgGetterUTCOffset, self.imgGetterOffSetValue + self.imgGetterUTCOffset ] isCorrect = True diff --git a/odmtools/view/clsBulkInsert.py b/odmtools/view/BulkInsertView.py similarity index 100% rename from odmtools/view/clsBulkInsert.py rename to odmtools/view/BulkInsertView.py diff --git a/tests/test_controller/test_bulkInsert.py b/tests/test_controller/test_bulkInsert.py index cdcfcbd..8036625 100644 --- a/tests/test_controller/test_bulkInsert.py +++ b/tests/test_controller/test_bulkInsert.py @@ -1,6 +1,6 @@ import wx import os -from odmtools.controller.frmBulkInsert import BulkInsertController +from odmtools.controller.BulkInsertController import BulkInsertController class TestBulkInsert: def setup(self): From 26c36cc377d896f99cb6e8adb811f89b6ec9c7f5 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 15 Nov 2016 12:44:38 -0700 Subject: [PATCH 077/158] create functions --- odmtools/odmservices/series_service.py | 67 ++++++++++++++------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 975c58d..c8893d1 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -693,41 +693,44 @@ def create_variable( # self._edit_session.commit() # return var # -# def create_qcl(self, code, definition, explanation): -# """ -# -# :param code: -# :param definition: -# :param explanation: -# :return: -# """ -# qcl = QualityControlLevel() -# qcl.code = code -# qcl.definition = definition -# qcl.explanation = explanation -# -# self._edit_session.add(qcl) -# self._edit_session.commit() -# return qcl -# -# -# def create_qualifier_by_qual(self, qualifier): -# self._edit_session.add(qualifier) -# self._edit_session.commit() -# return qualifier + def create_qcl(self, code, definition, explanation): + """ + + :param code: + :param definition: + :param explanation: + :return: + """ + qcl = QualityControlLevel() + qcl.code = code + qcl.definition = definition + qcl.explanation = explanation + + return self.create.createProcessingLevel(qcl) + # self._edit_session.add(qcl) + # self._edit_session.commit() + # return qcl # -# def create_qualifier(self, code, description): -# """ # -# :param code: -# :param description: -# :return: -# """ -# qual = Qualifier() -# qual.code = code -# qual.description = description + def create_qualifier_by_qual(self, qualifier): + # self._edit_session.add(qualifier) + # self._edit_session.commit() + # return qualifier + return self.create.createAnnotations(qualifier) # -# return self.create_qualifier_by_qual(qual) + def create_qualifier(self, code, description): + # """ + # + # :param code: + # :param description: + # :return: + # """ + qual = Qualifier() + qual.code = code + qual.description = description + # + # return self.create_qualifier_by_qual(qual) + return self.create.createAnnotations(qual); # # ##################### # # From 7be7efc69683d3be1b338bb6151deaa6c3dba58e Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 16 Nov 2016 12:48:46 -0700 Subject: [PATCH 078/158] Added a way to visualize if the entry is valid --- odmtools/controller/logicCellEdit.py | 35 +++++++++------- odmtools/controller/olvAddPoint.py | 61 ++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 71cbc6b..c9a6504 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -178,8 +178,6 @@ def imgGetterUTCOFFset(self, point): return "error" def imgGetterValueAcc(self, point): - """ - """ value = point.valueAccuracy point.validValueAcc = False if not value: @@ -201,8 +199,6 @@ def imgGetterValueAcc(self, point): return "error" def imgGetterOffSetType(self, point): - """ - """ point.validOffSetType = False if not point.offSetType in self.offSetTypeChoices: return "error" @@ -210,9 +206,6 @@ def imgGetterOffSetType(self, point): return "check" def imgGetterOffSetValue(self, point): - """ - """ - point.validOffSetValue = False if point.offSetValue == NULL: point.validOffSetValue = True @@ -235,15 +228,30 @@ def imgGetterOffSetValue(self, point): return "check" return "error" + def imgGetterQualityCode(self, point): + point.validQualityCode = False + if not point.qualityCodeCV in self.qualityCodeChoices or point.qualityCodeCV == NULL: + return "error" + point.validQualityCode = True + return "check" - def imgGetterQualifierCode(self, point): - """ - """ + def imgGetterTimeAggregationInterval(self, point): + point.validTimeAggInterval = False - point.validQualifierCode = False - if not point.qualifierCode in self.qualifierCodeChoices: + if point.timeAggInterval == NULL: return "error" - point.validQualifierCode = True + + point.validTimeAggInterval = True + return "check" + + def imgGetterTimeAggregationUnit(self, point): + point.validTimeAggUnit = False + + if not point.timeAggregationUnitID in self.timeAggretaionUnitChoices or point.timeAggregationUnitID == NULL: + return "error" + + point.validTimeAggUnit = True + return "check" """ @@ -271,7 +279,6 @@ def valueSetterUTCOffset(self, point, newValue): point.utcOffSet = newValue - """ ------------------------ Custom String Converters diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 6d3bb4d..8bdb315 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -38,6 +38,9 @@ def __init__(self, data_value="-9999", date=datetime.now().date(), time="00:00:0 self.validCensorCode = False self.validValueAcc = False self.validOffSetType = False + self.validQualityCode = False + self.validTimeAggInterval = False + self.validTimeAggUnit = False class OLVAddPoint(FastObjectListView): @@ -67,7 +70,9 @@ def __init__(self, *args, **kwargs): self.imgGetterTime = cellEdit.imgGetterTime self.imgGetterCensorCode = cellEdit.imgGetterCensorCode self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset - # self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue + self.imgGetterQualityCode = cellEdit.imgGetterQualityCode + self.imgGetterTimeAggInterval = cellEdit.imgGetterTimeAggregationInterval + self.imgGetterTimeAggUnit = cellEdit.imgGetterTimeAggregationUnit ## Custom Value Setters ## Sets the value, can modify rules for setting value @@ -104,44 +109,66 @@ def __init__(self, *args, **kwargs): def buildOlv(self): columns = [ - ColumnDefn("DataValue", "left", -1, minimumWidth=100, + ColumnDefn(title="DataValue", + minimumWidth=100, valueGetter='dataValue', valueSetter=self.valueSetterDataValue, imageGetter=self.imgGetterDataValue, stringConverter=self.str2DataValue, headerImage="star"), - ColumnDefn("Date", "left", -1, minimumWidth=120, + + ColumnDefn(title="Date", minimumWidth=120, valueGetter="date", imageGetter=self.imgGetterDate, cellEditorCreator=self.dateEditor, headerImage="star"), - ColumnDefn("Time", "left", -1, minimumWidth=100, + + ColumnDefn(title="Time", minimumWidth=100, valueGetter="time", imageGetter=self.imgGetterTime, cellEditorCreator=self.timeEditor, stringConverter=self.localtime2Str, headerImage="star"), - ColumnDefn("UTCOffset", "left", -1, minimumWidth=100, valueGetter="utcOffSet", - imageGetter=self.imgGetterUTCOffset, headerImage="star"), - ColumnDefn("CensorCode", "left", -1, valueGetter="censorCode", minimumWidth=110, - cellEditorCreator=self.censorEditor, imageGetter=self.imgGetterCensorCode, headerImage="star"), + ColumnDefn(title="UTCOffset", + minimumWidth=100, + valueGetter="utcOffSet", + imageGetter=self.imgGetterUTCOffset, + headerImage="star"), + + ColumnDefn(title="CensorCode", + valueGetter="censorCode", + minimumWidth=110, + cellEditorCreator=self.censorEditor, + imageGetter=self.imgGetterCensorCode, + headerImage="star"), - ColumnDefn(title="Quality CodeCV", align="left", valueGetter="qualityCodeCV", - minimumWidth=130, cellEditorCreator=self.qualityCodeCreator, imageGetter="star"), + ColumnDefn(title="Quality CodeCV", + valueGetter="qualityCodeCV", + minimumWidth=130, + cellEditorCreator=self.qualityCodeCreator, + imageGetter=self.imgGetterQualityCode, + headerImage="star"), - ColumnDefn(title="TimeAggregationInterval", align="left", minimumWidth=130, - valueGetter="timeAggInterval", headerImage="star"), + ColumnDefn(title="TimeAggregationInterval", + minimumWidth=130, + valueGetter="timeAggInterval", + imageGetter=self.imgGetterTimeAggInterval, + headerImage="star"), - ColumnDefn(title="TimeAggregationUnitID", align="left", minimumWidth=130, - valueGetter="timeAggregationUnitID", cellEditorCreator=self.timeAggregationUnitIDCreator, headerImage="star"), + ColumnDefn(title="TimeAggregationUnitID", + minimumWidth=130, + valueGetter="timeAggregationUnitID", + cellEditorCreator=self.timeAggregationUnitIDCreator, + imageGetter=self.imgGetterTimeAggUnit, headerImage="star"), - ColumnDefn(title="Annotation", align="left", minimumWidth=130, - valueGetter="annotation", cellEditorCreator=self.annotationCreator, headerImage="star") + ColumnDefn(title="Annotation", + minimumWidth=130, + valueGetter="annotation", + cellEditorCreator=self.annotationCreator) ] self.SetColumns(columns) - self.SetObjects(None) def rowFormatter(listItem, point): From 41200c69ce8953bcdfd075cafce4591ff9ccc5e3 Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 16 Nov 2016 17:24:41 -0700 Subject: [PATCH 079/158] remove erroneous pnlDataTable file --- odmtools/controller/olvDataTable.py | 3 +- odmtools/gui/frmODMTools.py | 2 +- odmtools/gui/pnlDataTable.py | 217 ---------------------------- tests/test_gui/test_pnlDataTable.py | 2 +- 4 files changed, 3 insertions(+), 221 deletions(-) delete mode 100644 odmtools/gui/pnlDataTable.py diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 2925cdd..d4806d3 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -25,9 +25,8 @@ def init(self, memDB): self.memDB = memDB columns = \ [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in x.lower() else '%s') + stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') for x, i in self.memDB.getEditColumns()] - print columns self.useAlternateBackColors = True self.oddRowsBackColor = wx.Colour(191, 217, 217) diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index b208b41..a54b180 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -13,7 +13,7 @@ import mnuRibbon import pnlPlot import pnlPlot -import pnlDataTable +# import pnlDataTable import wx.lib.agw.aui as aui import wx.py.crust import wx.stc diff --git a/odmtools/gui/pnlDataTable.py b/odmtools/gui/pnlDataTable.py deleted file mode 100644 index b2a137c..0000000 --- a/odmtools/gui/pnlDataTable.py +++ /dev/null @@ -1,217 +0,0 @@ -import wx -import wx.grid -import logging -import itertools as iter - -import pandas as pd -from odmtools.lib.ObjectListView import ColumnDefn, VirtualObjectListView, ObjectListView -from wx.lib.pubsub import pub as Publisher -import numpy as np -import timeit - - -# from odmtools.common.logger import LoggerTool -# -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -[wxID_PNLDATATABLE, wxID_PNLDATATABLEDATAGRID, -] = [wx.NewId() for _init_ctrls in range(2)] - - -class pnlDataTable(wx.Panel): - - toggle = iter.cycle([0, 1]).next - - def __init__(self, parent): - self.parent = parent - self._init_ctrls() - - def _init_ctrls(self): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLDATATABLE, name=u'pnlDataTable', - parent=self.parent, size=wx.Size(677, 449), - style=wx.TAB_TRAVERSAL) - # self.record_service = self.parent.Parent.getRecordService() - self.myOlv = VirtualObjectListView(self, style=wx.LC_REPORT) - - self.myOlv.SetEmptyListMsg("No Series Selected for Editing") - self.currentItem = None - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.myOlv, 1, wx.ALL | wx.EXPAND, 4) - self.SetSizer(sizer_2) - - self.myOlv.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected) - self.myOlv.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.onItemSelected) - - self.EnableSorting() - - Publisher.subscribe(self.onChangeSelection, "changeTableSelection") - Publisher.subscribe(self.onRefresh, "refreshTable") - Publisher.subscribe(self.onDeselectAll, "deselectAllDataTable") - - self.ascending = False - self.enableSelectDataTable = False - - self.Layout() - - - - # def toggleBindings(self): - # """ Activates/Deactivates Datatable specific bindings - # - # :param activate: - # :return: - # """ - # - # if self.toggle(): - # #logger.info("binding activated...") - # try: - # self.myOlv.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.onItemSelected, id=self.myOlv.GetId()) - # self.myOlv.Bind(wx.EVT_CHAR, self.onKeyPress, id=self.myOlv.GetId()) - # self.myOlv.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyPress, id=self.myOlv.GetId()) - # except: - # pass - # else: - # #logger.info("binding deactivated...") - # try: - # self.myOlv.Unbind(wx.EVT_LIST_ITEM_FOCUSED, self.onItemSelected, id=self.myOlv.GetId()) - # self.myOlv.Unbind(wx.EVT_CHAR, self.onKeyPress, id=self.myOlv.GetId()) - # self.myOlv.Unbind(wx.EVT_LIST_KEY_DOWN, self.onKeyPress, id=self.myOlv.GetId()) - # except: - # pass - - def init(self, memDB): - self.memDB = memDB - - columns = [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter= "%s" if "date" in x.lower() and "utc" not in x.lower() else '%s') - - #'%Y-%m-%d %H:%M:%S' - for x, i in self.memDB.getEditColumns()] - - self.myOlv.useAlternateBackColors = True - self.myOlv.oddRowsBackColor = wx.Colour(191, 217, 217) - - '''values = self.memDB.getDataValues() - value_length = len(values) - - self.myOlvDataFrame = pd.DataFrame(values, columns=[x.title for x in columns]) - ''' - self.myOlv.SetColumns(columns) - - self.myOlvDataFrame = self.memDB.getDataValuesDF() - sort_by_index = list(self.myOlvDataFrame.columns).index("LocalDateTime") - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[sort_by_index], inplace=True) - self.dataObjects = self.myOlvDataFrame.values.tolist() - - self.myOlv.SetObjectGetter(self.objectGetter) - self.myOlv.SetItemCount(len(self.myOlvDataFrame)) - - def EnableSorting(self): - self.myOlv.Bind(wx.EVT_LIST_COL_CLICK, self.onColSelected) - self.sortedColumnIndex = -1 - - if not self.myOlv.smallImageList: - self.myOlv.SetImageLists() - if (not self.myOlv.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and - self.myOlv.smallImageList.GetSize(0) == (16, 16)): - self.myOlv.RegisterSortIndicators() - - def objectGetter(self, index): - """ - A Virtual list has to have a callable installed that says which model object is shown - at a given index - """ - return self.dataObjects[index % len(self.dataObjects)] - - def onColSelected(self, evt): - """ - Allows users to sort by clicking on columns - """ - logger.debug("Column: %s" % evt.m_col) - self.sortColumn(evt.m_col) - - def sortColumn(self, selected_column): - oldSortColumnIndex = self.sortedColumnIndex - self.sortedColumnIndex = selected_column - ascending = self.myOlv.sortAscending - if ascending: - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[selected_column], inplace=True) - self.myOlv.sortAscending = False - elif not ascending: - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[selected_column], ascending=False, inplace=True) - self.myOlv.sortAscending = True - - self.myOlv._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) - - self.dataObjects = self.myOlvDataFrame.values.tolist() - if self.myOlv.GetItemCount: - itemFrom = self.myOlv.GetTopItem() - itemTo = self.myOlv.GetTopItem()+1 + self.myOlv.GetCountPerPage() - itemTo = min(itemTo, self.myOlv.GetItemCount()-1) - self.myOlv.RefreshItems(itemFrom, itemTo) - - def onRefresh(self, e): - self.myOlvDataFrame = self.memDB.getDataValuesDF() - self.dataObjects = self.myOlvDataFrame.values.tolist() - # self.myOlv.RefreshItems() - - def clear(self): - self.memDB = None - self.myOlv.DeleteAllItems() - self.myOlvDataFrame = None - self.dataObjects = None - - def onItemSelected(self, event): - """ - - Disable selecting of an item in the DataTable, only sorting is available - """ - if not self.enableSelectDataTable: - self.myOlv.SetItemState(event.m_itemIndex, 0, wx.LIST_STATE_SELECTED) - - def onDeselectAll(self): - - selected_item = self.myOlv.GetFirstSelected() - while selected_item != -1: - self.myOlv.SetItemState(selected_item, 0, wx.LIST_STATE_SELECTED) - selected_item = self.myOlv.GetNextSelected(selected_item) - - - def onChangeSelection(self, datetime_list=[]): - """ - Select values within - """ - self.onDeselectAll() - - if isinstance(datetime_list, pd.DataFrame): - try: - self.enableSelectDataTable = True - olv = self.myOlvDataFrame.set_index("LocalDateTime") - filtered_dataframe = self.myOlvDataFrame[olv.index.isin(datetime_list.index)] - results = np.where(self.myOlvDataFrame.index.isin(filtered_dataframe.index)) - - for i in results[0]: - self.myOlv.SetItemState(i, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - self.myOlv.Focus(results[0][0]) - self.enableSelectDataTable = False - except: - pass - - def onKeyPress(self, evt): - """Ignores Keypresses""" - pass - - def stopEdit(self): - self.clear() - - - - - - - - diff --git a/tests/test_gui/test_pnlDataTable.py b/tests/test_gui/test_pnlDataTable.py index ca01e76..5993699 100644 --- a/tests/test_gui/test_pnlDataTable.py +++ b/tests/test_gui/test_pnlDataTable.py @@ -1,6 +1,6 @@ from odmtools.controller.frmDataTable import FrmDataTable from odmtools.odmdata import MemoryDatabase, DataValue -from odmtools.gui.pnlDataTable import pnlDataTable + from odmtools.odmdata import SessionFactory from odmtools.odmservices import SeriesService from tests import test_util From 5617ad19ab33001fff7a03063eaafd931471f753 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 17 Nov 2016 11:55:01 -0700 Subject: [PATCH 080/158] Validate data inserting to database --- odmtools/controller/olvAddPoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index 8bdb315..a9cdd8f 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -185,7 +185,7 @@ def rowFormatter(listItem, point): def isCorrect(self, point): validators = [ self.imgGetterDataValue, self.imgGetterDate, self.imgGetterTime, self.imgGetterCensorCode, - self.imgGetterUTCOffset + self.imgGetterUTCOffset, self.imgGetterQualityCode, self.imgGetterTimeAggInterval, self.imgGetterTimeAggUnit ] isCorrect = True From 743ea512d973e4b2421f24ba708b00a8df478b39 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Mon, 21 Nov 2016 12:55:24 -0700 Subject: [PATCH 081/158] get all values commits --- odmtools/odmservices/series_service.py | 66 +++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index c8893d1..14b1876 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -408,42 +408,42 @@ def get_values(self, series_id=None): data.set_index(data['valuedatetime'], inplace=True) return data - # def get_all_values_df(self): + def get_all_values_df(self): - # """ - # - # :return: Pandas DataFrame object - # """ -# q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) -# query = q.statement.compile(dialect=self._session_factory.engine.dialect) -# data = pd.read_sql_query(sql=query, con=self._session_factory.engine, -# params=query.params) -# columns = list(data) -# -# columns.insert(0, columns.pop(columns.index("DataValue"))) -# columns.insert(1, columns.pop(columns.index("LocalDateTime"))) -# columns.insert(2, columns.pop(columns.index("QualifierID"))) -# -# data = data.ix[:, columns] -# return data.set_index(data['LocalDateTime']) -# q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) -# query = q.statement.compile(dialect = self._session_factory.engine.dialect) -# data = pd.read_sql_query(sql= query, -# con= self._session_factory.engine, -# params=query.params) -# + """ + :return: Pandas DataFrame object + """ + q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, con=self._session_factory.engine, + params=query.params) + columns = list(data) + + # columns.insert(0, columns.pop(columns.index("DataValue"))) + # columns.insert(1, columns.pop(columns.index("ValueDateTime"))) + #columns.insert(2, columns.pop(columns.index("QualifierID"))) + + data = data.ix[:, columns] + return data.set_index(data['ValueDateTime']) + # q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) + # query = q.statement.compile(dialect = self._session_factory.engine.dialect) + # data = pd.read_sql_query(sql= query, + # con= self._session_factory.engine, + # params=query.params) # -# def get_all_values_list(self): -# """ -# -# :return: -# """ -# result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() -# return [x.list_repr() for x in result] -# -# def get_all_values(self): -# return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() + + + def get_all_values_list(self): + """ + + :return: + """ + result = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() + return [x.list_repr() for x in result] + + def get_all_values(self): + return self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() # @staticmethod def calcSeason(row): From f1f055fade46fcd427953175058f54e4f7d4a56f Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 21 Nov 2016 16:49:59 -0700 Subject: [PATCH 082/158] fix datatable formatting error --- odmtools/controller/olvDataTable.py | 16 +++++++++------- odmtools/odmdata/memory_database.py | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index d4806d3..112f4d7 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -23,18 +23,20 @@ def __init__(self, parent, **kwargs): def init(self, memDB): self.memDB = memDB - columns = \ - [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') - for x, i in self.memDB.getEditColumns()] self.useAlternateBackColors = True self.oddRowsBackColor = wx.Colour(191, 217, 217) - self.SetColumns(columns) + self.dataframe = self.memDB.getDataValuesDF() sort_by_index = list(self.dataframe.columns).index("valuedatetime") self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) self.dataObjects = self.dataframe.values.tolist() + columns = \ + [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, + stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') + for x, i in self.memDB.getEditColumns()] + self.SetColumns(columns) + self.SetObjectGetter(self.ObjectGetter) self.SetItemCount(len(self.dataframe)) @@ -73,10 +75,10 @@ def sortColumn(self, selected_column): self.sortedColumnIndex = selected_column ascending = self.sortAscending if ascending: - self.dataframe.sort(self.dataframe.columns[selected_column], inplace=True) + self.dataframe.sort_values(self.dataframe.columns[selected_column], inplace=True) self.sortAscending = False elif not ascending: - self.dataframe.sort(self.dataframe.columns[selected_column], ascending=False, inplace=True) + self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=False, inplace=True) self.sortAscending = True self._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 5403550..e52f463 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -80,11 +80,11 @@ def getEditRowCount(self): def getEditColumns(self): columns = [] tmp_columns = self.df.columns.tolist() - tmp_columns.remove('datavalue') - tmp_columns.remove('valuedatetime') + # tmp_columns.remove('datavalue') + # tmp_columns.remove('valuedatetime') #tmp_columns.remove('QualifierID') - columns.append('datavalue') - columns.append('valuedatetime') + # columns.append('datavalue') + # columns.append('valuedatetime') #columns.append('QualifierID') columns.extend(tmp_columns) return [(x, i) for (i, x) in enumerate(columns)] From 44713b5dfe470f481e4475614e21a144f7744180 Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 22 Nov 2016 11:16:16 -0700 Subject: [PATCH 083/158] get qcl api calls --- odmtools/odmservices/series_service.py | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 14b1876..f4ec11b 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -300,20 +300,23 @@ def get_all_processing_levels(self): # return self._edit_session.query(Qualifier).join(subquery).distinct().all() # # #QCL methods -# def get_all_qcls(self): -# return self._edit_session.query(QualityControlLevel).all() + def get_all_qcls(self): + return self.read.getProcessingLevels(); +# return self._edit_session.query(QualityControlLevel).all() # -# def get_qcl_by_id(self, qcl_id): -# try: -# return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() -# except: -# return None + def get_qcl_by_id(self, qcl_id): + try: + return self.read.getProcessingLevels(ids = [qcl_id])[0] + #return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() + except: + return None # -# def get_qcl_by_code(self, qcl_code): -# try: -# return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() -# except: -# return None + def get_qcl_by_code(self, qcl_code): + try: + return self.read.getProcessingLevels(codes=[qcl_code])[0] + #return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() + except: + return None # # # Method methods def get_all_methods(self): From 0fd7217ac400098ba6fde3a186a7744879163eee Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 22 Nov 2016 13:02:27 -0700 Subject: [PATCH 084/158] Updated the flag values Includes a redesign and refactor of the flag values gui. Using MVC style. Creating an annotation works. Bug fixes --- .../controller/NewFlagValuesController.py | 40 ++++++++++++ odmtools/controller/logicCellEdit.py | 6 -- odmtools/gui/mnuRibbon.py | 19 +++--- odmtools/odmservices/series_service.py | 17 +++-- odmtools/view/NewFlagValuesView.py | 65 +++++++++++++++++++ 5 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 odmtools/controller/NewFlagValuesController.py create mode 100644 odmtools/view/NewFlagValuesView.py diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py new file mode 100644 index 0000000..1ce2c1d --- /dev/null +++ b/odmtools/controller/NewFlagValuesController.py @@ -0,0 +1,40 @@ +import wx +from odmtools.view.NewFlagValuesView import NewFlagValuesView + + +class NewFlagValuesController(NewFlagValuesView): + def __init__(self, parent, series_service, qualifier_choice, record_service): + + NewFlagValuesView.__init__(self, parent) + self.series_service = series_service + self.qualifer_choice = qualifier_choice + self.record_service = record_service + + annotations = self.series_service.get_all_annotations() + self.append_items_to_annotation(annotations) + self.annotation_combo.SetSelection(0) + + self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel) + self.ok_button.Bind(wx.EVT_BUTTON, self.on_ok) + self.MakeModal(True) + + def append_items_to_annotation(self, annotations): + self.annotation_combo.Append("[New Annontation]") + if not isinstance(annotations, list): + print "type(annotations) must be list of annotations" + return + + for item in annotations: + self.annotation_combo.Append(item.AnnotationCode + item.AnnotationText) + + def on_cancel(self, event): + self.MakeModal(False) + self.Destroy() + event.Skip() + + def on_ok(self, event): + code = self.code_textbox.GetValue() + text = self.text_textbox.GetValue() + + self.series_service.create_annotation(code, text) + self.on_cancel(event) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index c9a6504..ed60038 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -1,14 +1,8 @@ -""" - ADD Point Cell Editor Logic -""" from collections import OrderedDict import datetime - import wx import wx.combo from wx.lib import masked -from odmtools.gui.frmFlagValues import frmFlagValues -from odmtools.lib.ObjectListView import CellEditor __author__ = 'Jacob' diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index f5b11a0..6c21874 100644 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -11,7 +11,8 @@ from odmtools.controller.frmDataFilters import frmDataFilter from odmtools.controller.frmChangeValue import frmChangeValue -from frmFlagValues import frmFlagValues +# from frmFlagValues import frmFlagValues +from odmtools.controller.NewFlagValuesController import NewFlagValuesController from odmtools.controller.frmLinearDrift import frmLinearDrift from odmtools.controller.frmAbout import frmAbout from odmtools.controller.frmGapFill import frmGapFill @@ -446,13 +447,15 @@ def onEditFlag(self, event): series_service = serviceManager.get_series_service() qualifierChoices = OrderedDict((x.AnnotationCode + '-' + x.AnnotationText, x.AnnotationID) for x in series_service.get_all_qualifiers() if x.AnnotationCode and x.AnnotationText) - add_flag = frmFlagValues(self.parent, series_service, qualifierChoices) - val = add_flag.ShowModal() - - if val == wx.ID_OK: - logger.debug("FLAG Value: %s, type: %s" % (val, type(val))) - self.parent.getRecordService().flag(add_flag.GetValue()) - add_flag.Destroy() + # add_flag = frmFlagValues(self.parent, series_service, qualifierChoices) + add_flag_controller = NewFlagValuesController(self, series_service, qualifierChoices, self.parent.getRecordService()) + # val = add_flag.ShowModal() + add_flag_controller.Show() + + # if val == wx.ID_OK: + # logger.debug("FLAG Value: %s, type: %s" % (val, type(val))) + # self.parent.getRecordService().flag(add_flag.GetValue()) + # add_flag.Destroy() event.Skip() # ################################### diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index f4ec11b..1708972 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -4,6 +4,7 @@ from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * +import datetime from odmtools.common.logger import LoggerTool import pandas as pd logger =logging.getLogger('main') @@ -992,16 +993,24 @@ def create_processing_level(self, code, definition, explanation): def create_annotation_by_anno(self, annotation): return self.create.createAnnotations(annotation) - def create_annotation(self, code, description): + def create_annotation(self, code, text, link=None): """ :param code: - :param description: + :param text: :return: """ annotation = Annotations() annotation.AnnotationCode = code - annotation.AnnotationText = description - annotation.AnnotationTypeCV = "timeSeriesResultValueAnnotation" + annotation.AnnotationText = text + annotation.AnnotationTypeCV = "Time series result value annotation" + current_time = datetime.datetime.now() + utc_time = datetime.datetime.utcnow() + annotation.AnnotationDateTime = current_time + + difference_in_timezone = utc_time - current_time + offset_in_hours = difference_in_timezone.seconds / 3600 + annotation.AnnotationUTCOffset = offset_in_hours + annotation.AnnotationLink = link return self.create_annotation_by_anno(annotation) diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py new file mode 100644 index 0000000..73bdca3 --- /dev/null +++ b/odmtools/view/NewFlagValuesView.py @@ -0,0 +1,65 @@ +import wx + + +class NewFlagValuesView(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, title="Flag Values", style=wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + + panel = wx.Panel(self) + content_panel = wx.Panel(panel) + bottom_panel = wx.Panel(panel) + + ########################################## + # CONTENT PANEL + ########################################## + + annotation_title = wx.StaticText(content_panel, label="Annotation") + self.annotation_combo = wx.ComboBox(content_panel, style=wx.CB_READONLY | wx.CB_SORT) + code_title = wx.StaticText(content_panel, label="Code") + self.code_textbox = wx.TextCtrl(content_panel, size=(100, -1)) + text_title = wx.StaticText(content_panel, label="Text") + self.text_textbox = wx.TextCtrl(content_panel) + link_text = wx.StaticText(content_panel, label="Link") + self.link_textbox = wx.TextCtrl(content_panel) + + content_panel_sizer = wx.BoxSizer(wx.VERTICAL) + content_panel_sizer.Add(annotation_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.annotation_combo, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + content_panel_sizer.Add(code_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.code_textbox, 0, wx.LEFT | wx.RIGHT, 10) + content_panel_sizer.Add(text_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.text_textbox , 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + content_panel_sizer.Add(link_text, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.link_textbox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + + content_panel.SetSizer(content_panel_sizer) + + ########################################## + # BOTTOM PANEL + ########################################## + + self.ok_button = wx.Button(bottom_panel, label="OK") + self.cancel_button = wx.Button(bottom_panel, label="CANCEL") + static_line = wx.StaticLine(bottom_panel) + + bottom_panel_sizer = wx.BoxSizer(wx.VERTICAL) + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.AddSpacer((0, 0), 1, wx.EXPAND, 2) + button_sizer.Add(self.ok_button, 0, wx.EXPAND | wx.ALL ^ wx.RIGHT, 5) + button_sizer.Add(self.cancel_button, 0, wx.EXPAND | wx.ALL, 5) + + bottom_panel_sizer.Add(static_line, 0, wx.EXPAND) + bottom_panel_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT) + + bottom_panel.SetSizer(bottom_panel_sizer) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(content_panel, 1, wx.EXPAND | wx.ALL, 0) + main_sizer.Add(bottom_panel, 0, wx.EXPAND | wx.ALL, 0) + + panel.SetSizer(main_sizer) + main_sizer.Fit(self) + self.SetSize((400, 300)) + + + From d1a35cafde97bd060a3fb5feabdf472178be9510 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 22 Nov 2016 13:04:35 -0700 Subject: [PATCH 085/158] fix right click --- odmtools/controller/frmSeriesSelector.py | 2 +- odmtools/odmdata/memory_database.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index 6a259b4..4a46d46 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -164,7 +164,7 @@ def OnTableRightDown(self, event): # build pop-up menu for right-click display self.selectedIndex = event.m_itemIndex #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) - self.selectedID = self.tblSeries.GetSelectedObject().resultID + self.selectedID = self.tblSeries.GetSelectedObject().ResultID # print self.selectedID popup_edit_series = wx.NewId() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index e52f463..0c5a064 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -80,12 +80,7 @@ def getEditRowCount(self): def getEditColumns(self): columns = [] tmp_columns = self.df.columns.tolist() - # tmp_columns.remove('datavalue') - # tmp_columns.remove('valuedatetime') - #tmp_columns.remove('QualifierID') - # columns.append('datavalue') - # columns.append('valuedatetime') - #columns.append('QualifierID') + columns.extend(tmp_columns) return [(x, i) for (i, x) in enumerate(columns)] # return [(x, i) for (i, x) in enumerate(self.df.columns)] From e78bdb497c02b780a400fa57c6ac52cc11e105b1 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 22 Nov 2016 13:37:26 -0700 Subject: [PATCH 086/158] Bug fixes with add points and flag values --- .../controller/NewFlagValuesController.py | 16 +++++++++---- odmtools/controller/logicCellEdit.py | 23 ++++++++++--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py index 1ce2c1d..a090d4c 100644 --- a/odmtools/controller/NewFlagValuesController.py +++ b/odmtools/controller/NewFlagValuesController.py @@ -9,23 +9,25 @@ def __init__(self, parent, series_service, qualifier_choice, record_service): self.series_service = series_service self.qualifer_choice = qualifier_choice self.record_service = record_service + self.__new_annotation = "[New Annontation]" annotations = self.series_service.get_all_annotations() self.append_items_to_annotation(annotations) self.annotation_combo.SetSelection(0) self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel) + self.Bind(wx.EVT_CLOSE, self.on_cancel) self.ok_button.Bind(wx.EVT_BUTTON, self.on_ok) self.MakeModal(True) def append_items_to_annotation(self, annotations): - self.annotation_combo.Append("[New Annontation]") + self.annotation_combo.Append(self.__new_annotation) if not isinstance(annotations, list): print "type(annotations) must be list of annotations" return for item in annotations: - self.annotation_combo.Append(item.AnnotationCode + item.AnnotationText) + self.annotation_combo.Append(str(item.AnnotationID)) def on_cancel(self, event): self.MakeModal(False) @@ -33,8 +35,12 @@ def on_cancel(self, event): event.Skip() def on_ok(self, event): - code = self.code_textbox.GetValue() - text = self.text_textbox.GetValue() + selection = self.annotation_combo.GetValue() + if selection == self.__new_annotation: + code = self.code_textbox.GetValue() + text = self.text_textbox.GetValue() + + annotation = self.series_service.create_annotation(code, text) + self.record_service.flag(annotation.AnnotationID) - self.series_service.create_annotation(code, text) self.on_cancel(event) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index ed60038..1718c59 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -9,7 +9,7 @@ #### Options #### utcOffSetBounds = (-12, 12) NULL = "NULL" -NEW = "[New Qualifier]" +NEW = "[New Annotation]" class CellEdit(): def __init__(self, parent, serviceManager, recordService): @@ -18,21 +18,22 @@ def __init__(self, parent, serviceManager, recordService): self.serviceManager = serviceManager if self.serviceManager: self.series_service = self.serviceManager.get_series_service() - self.qualifierChoices = OrderedDict((x.code + ':' + x.description, x.id) - for x in self.series_service.get_all_qualifiers() if x.code and x.description) - self.qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] - + self.annotationChoices = self.fetch_annotations() + self.censorCodeChoices = self.fetchCensorCodeChoices() + self.qualityCodeChoices = self.fetchQualityCodeChoices() + self.timeAggregationInterval = -1 + self.timeAggretaionUnitChoices = self.fetchTimeUnitChoices() else: self.censorCodeChoices = [NULL] + ['SampleCensorCode1'] + ['SampleCensorCode2'] + ['SampleCensorCode3'] self.labSampleChoices = [NULL] + ['SampleLabSample1'] + ['SampleLabSample2'] + ['SampleLabSample3'] self.offSetTypeChoices = [NULL] + ['SampleOffsetType1'] + ['SampleOffsetType2'] + ['SampleOffsetType3'] - self.qualifierCodeChoices = [NULL] + ['SampleQualifierCode1'] + ['SampleQualifierCode2'] + ['SampleQualifierCode3'] + self.annotationChoices = [NULL] + ['SampleAnnotation1'] + ['SampleAnnotation2'] + ['SampleAnnotation3'] - self.qualityCodeChoices = self.fetchQualityCodeChoices() - self.censorCodeChoices = self.fetchCensorCodeChoices() - self.timeAggregationInterval = -1 - self.timeAggretaionUnitChoices = self.fetchTimeUnitChoices() - self.annotationChoices = [NULL] + def fetch_annotations(self): + qualifierChoices = OrderedDict((x.AnnotationCode + ':' + x.AnnotationText, x.AnnotationID) + for x in self.series_service.get_all_qualifiers() if x.AnnotationCode and x.AnnotationText) + qualifierCodeChoices = [NULL] + qualifierChoices.keys() + [NEW] + return qualifierCodeChoices def fetchCensorCodeChoices(self): if not self.serviceManager: From c482afb82066cecb2b827b41a394cc795f087272 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 23 Nov 2016 13:31:19 -0700 Subject: [PATCH 087/158] Multiple bug fixes with the save wizard Most of bugs are due to the update --- ODMTools.py | 1 + .../controller/NewFlagValuesController.py | 2 +- odmtools/controller/WizardMethodController.py | 76 ++++++++++++++----- .../WizardProcessLevelController.py | 36 ++++++--- .../controller/WizardVariableController.py | 3 +- odmtools/gui/wizSave.py | 60 ++++++++------- odmtools/odmservices/series_service.py | 25 +++--- odmtools/view/NewFlagValuesView.py | 1 + 8 files changed, 129 insertions(+), 75 deletions(-) diff --git a/ODMTools.py b/ODMTools.py index 6b2014b..14fc8ee 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -57,6 +57,7 @@ def OnInit(self): kwargs['taskServer'] = self.taskserver kwargs['memdb']= self.memdb self.frame = frmODMToolsMain(**kwargs) + self.frame.CenterOnScreen() self.frame.Show(True) app= self.frame return True diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py index a090d4c..f27bfe9 100644 --- a/odmtools/controller/NewFlagValuesController.py +++ b/odmtools/controller/NewFlagValuesController.py @@ -27,7 +27,7 @@ def append_items_to_annotation(self, annotations): return for item in annotations: - self.annotation_combo.Append(str(item.AnnotationID)) + self.annotation_combo.Append(str(item.AnnotationCode + ":" + item.AnnotationText)) def on_cancel(self, event): self.MakeModal(False) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index dc51961..88eaee5 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -14,9 +14,9 @@ def __init__(self, parent, series_service): self.SetSizer(main_sizer) self.series_service = series_service - table_columns = ["Descriptions", "Link", "ID"] + table_columns = ["ID", "Descriptions", "Link", "Code", "Type"] + self.cv_types = [] self.method_view.existing_method_table.set_columns(table_columns) - self.method_view.method_type_combo.AppendItems(["ABC"]) self.on_auto_radio(None) self.method_view.auto_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_auto_radio) @@ -24,6 +24,7 @@ def __init__(self, parent, series_service): self.method_view.create_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_method_radio) self.__fetch_data() + self.method_view.method_type_combo.AppendItems(self.cv_types) def on_auto_radio(self, event): self.method_view.existing_method_table.Enable(False) @@ -53,28 +54,61 @@ def __fetch_data(self): data = [] for meth in methods: data.append([ - meth.MethodDescription, - meth.MethodLink, - meth.MethodID + meth.MethodID, meth.MethodDescription, + meth.MethodLink, meth.MethodCode, + meth.MethodTypeCV ]) + if meth.MethodTypeCV not in self.cv_types: + self.cv_types.append(meth.MethodTypeCV) + self.method_view.existing_method_table.set_table_content(data=data) def getMethod(self): - m = Method() if self.method_view.auto_method_radio.GetValue(): - description = "Values derived from ODM Tools Python" - m = self.series_service.get_method_by_code(description) - if m is None: - m = Method() - m.description = description - elif self.method_view.existing_method_radio.GetValue(): - index = self.method_view.existing_method_table.GetFirstSelected() - desc = self.method_view.existing_method_table.GetItem(index, 0).GetText() - - m = self.series_service.get_method_by_code(desc) - elif self.method_view.create_method_radio.GetValue(): - m.description = self.method_view.description_text_ctrl.GetValue() - - - return m + return self.__auto_generate_a_method() + + if self.method_view.existing_method_radio.GetValue(): + return self.__select_existing_method() + + if self.method_view.create_method_radio.GetValue(): + return self.__create_new_method() + + return None + + def __auto_generate_a_method(self): + code = "odmtools" + method = self.series_service.get_method_by_code(method_code=code) + if method is None: + method = Method() + method.MethodCode = code + method.MethodDescription = "Values derived from ODM Tools Python" + return method + + def __select_existing_method(self): + index = self.method_view.existing_method_table.GetFirstSelected() + desc = self.method_view.existing_method_table.GetItem(index, 1).GetText() + link = self.method_view.existing_method_table.GetItem(index, 2).GetText() + code = self.method_view.existing_method_table.GetItem(index, 3).GetText() + + method = self.series_service.get_method_by_code(method_code=code) + method.MethodLink = link + method.MethodDescription = desc + return method + + def __create_new_method(self): + code = self.method_view.method_code_text_ctrl.GetValue() + name = self.method_view.method_name_text_ctrl.GetValue() + typeCV = self.method_view.method_type_combo.GetValue() + # organization = self.method_view.organization_combo.GetValue() + link = self.method_view.method_link_text_ctrl.GetValue() + description = self.method_view.description_text_ctrl.GetValue() + + method = Method() + method.MethodCode = code + method.MethodName = name + method.MethodTypeCV = typeCV + method.MethodLink = link + method.MethodDescription = description + + return method diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 8ac705c..8f53e1f 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -1,8 +1,7 @@ import wx from odmtools.view.WizardProcessLevelView import WizardProcessLevelView from wx.wizard import WizardPageSimple -# from odmtools.odmdata import QualityControlLevel -from odm2api.ODM2.models import ProcessingLevels as QualityControlLevel +from odm2api.ODM2.models import ProcessingLevels class WizardProcessLevelController(WizardPageSimple): @@ -53,16 +52,29 @@ def __fetch_data(self): self.processing_level_view.existing_process_table.set_table_content(data=data) - def getQCL(self): - q = QualityControlLevel() + def get_processing_level(self): if self.processing_level_view.create_process_level_radio.GetValue(): - q.code = self.processing_level_view.level_code_text_ctrl.GetValue() - q.definition = self.processing_level_view.definition_text_ctrl.GetValue() - q.explanation = self.processing_level_view.explanation_text_ctrl.GetValue() + return self.__select_existing_processing_level() - elif self.processing_level_view.existing_process_radio.GetValue(): - selected_row = self.processing_level_view.existing_process_table.get_selected_row() - code = selected_row[0] - q = self.service_manager.get_series_service().get_processing_level_by_code(proc_level_code=code) + if self.processing_level_view.existing_process_radio.GetValue(): + return self.__select_existing_processing_level() - return q + return None + + def __select_existing_processing_level(self): + selected_row = self.processing_level_view.existing_process_table.get_selected_row() + code = selected_row[0] + proc_level = self.service_manager.get_series_service().get_processing_level_by_code(codes=code) + return proc_level + + def __create_processing_level(self): + code = self.processing_level_view.level_code_text_ctrl.GetValue() + definition = self.processing_level_view.definition_text_ctrl.GetValue() + explanation = self.processing_level_view.explanation_text_ctrl.GetValue() + + proc = ProcessingLevels() + proc.ProcessingLevelCode = code + proc.Definition = definition + proc.Explanation = explanation + + return proc diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index e0d2fb1..f20674a 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -83,8 +83,7 @@ def get_variable(self): v = self.service_manager.get_series_service().get_variable_by_code(code) elif self.variable_view.create_variable_radio.GetValue(): - # v = self.createdVar - v = self.get_new_variable() + return self.get_new_variable() return v diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index ee2796b..06bdb1d 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -182,34 +182,34 @@ def __init__(self, parent, title, series_service): def fill_summary(self): - Site, Variable, Method, Source, QCL = self.parent.get_metadata() + sampling_feature, variable, method, source, processing_level = self.parent.get_metadata() ## self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, "Code: "+ str(QCL.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(Site.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(Site.name)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(sampling_feature.SamplingFeatureCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(sampling_feature.SamplingFeatureName)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vc, 'Code: ' + str(Variable.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vn, 'Name: ' + str(Variable.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vu, 'Units: ' + str(Variable.variable_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vsm, 'Sample Medium: ' + str(Variable.sample_medium)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vvt, 'Value Type: ' + str(Variable.value_type)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vts, 'Time Support: ' + str(Variable.time_support)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vtu, 'Time Units: ' + str(Variable.time_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vdt, 'Data Type: ' + str(Variable.data_type)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vc, 'Code: ' + str(variable.VariableCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vn, 'Name: ' + str(variable.VariableNameCV)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vu, 'Units: ' + str(variable.variable_unit.name)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vsm, 'Sample Medium: ' + str(variable.sample_medium)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vvt, 'Value Type: ' + str(variable.value_type)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vts, 'Time Support: ' + str(variable.time_support)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vtu, 'Time Units: ' + str(variable.time_unit.name)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vdt, 'Data Type: ' + str(variable.data_type)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.vgc, - 'General Category: ' + str(Variable.general_category)) + 'General Category: ' + str(variable.general_category)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(Method.description)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.description)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(Source.organization)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(Source.description)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(Source.citation)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(source.organization)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(source.description)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(source.citation)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(QCL.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(QCL.definition)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(QCL.explanation)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.code)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.definition)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(processing_level.explanation)) self.panel.treeSummary.ExpandAll() @@ -235,25 +235,29 @@ def _init_ctrls(self, prnt): self.Bind(wx.wizard.EVT_WIZARD_FINISHED, self.on_wizard_finished) def get_metadata(self): + method = None + variable = None + processing_level = None + source = None if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): logger.debug("SaveAs") method = self.pgMethod.getMethod() - qcl = self.pgQCL.getQCL() + processing_level = self.pgQCL.get_processing_level() variable = self.pgVariable.get_variable() elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): logger.debug("Save") - method = self.currSeries.method - qcl = self.currSeries.quality_control_level + method = self.currSeries.FeatureActionObj.ActionObj.MethodObj + processing_level = self.currSeries.quality_control_level variable = self.currSeries.variable elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): logger.debug("Existing") - method, qcl, variable = self.pgExisting.getSeries() - site = self.currSeries.site - source = self.currSeries.source - logger.debug("site: %s, variable: %s, method: %s, source: %s, qcl: %s" % ( - str(site), str(variable), str(method), str(source), str(qcl))) - return site, variable, method, source, qcl + method, processing_level, variable = self.pgExisting.getSeries() + site = self.currSeries.FeatureActionObj.SamplingFeatureObj + # source = self.currSeries.source + logger.debug("site: %s, variable: %s, method: %s, source: %s, processing_level: %s" % ( + str(site), str(variable), str(method), str(source), str(processing_level))) + return site, variable, method, source, processing_level def __init__(self, parent, service_manager, record_service): self._init_ctrls(parent) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 1708972..b43b39e 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -300,25 +300,24 @@ def get_all_processing_levels(self): # Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() # return self._edit_session.query(Qualifier).join(subquery).distinct().all() # -# #QCL methods - def get_all_qcls(self): - return self.read.getProcessingLevels(); + # Processing Level methods + def get_all_processing_level_(self): + return self.read.getProcessingLevels() # return self._edit_session.query(QualityControlLevel).all() -# - def get_qcl_by_id(self, qcl_id): + + def get_processing_level_by_id(self, qcl_id): try: return self.read.getProcessingLevels(ids = [qcl_id])[0] #return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() except: return None # - def get_qcl_by_code(self, qcl_code): + def get_processing_level_by_code(self, codes): try: - return self.read.getProcessingLevels(codes=[qcl_code])[0] - #return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() + return self.read.getProcessingLevels(codes=[codes])[0] except: return None -# + # # Method methods def get_all_methods(self): #return self._edit_session.query(Method).all() @@ -332,8 +331,12 @@ def get_method_by_id(self, method_id): # result = None # return result # - def get_method_by_description(self, method_code): - return self.read.getMethods(codes=[method_code])[0] + def get_method_by_code(self, method_code): + try: + self.read.getMethods(codes=[method_code])[0] + except: + return None + # try: # result = self._edit_session.query(Method).filter_by(description=method_code).first() # except: diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py index 73bdca3..2bbfcd3 100644 --- a/odmtools/view/NewFlagValuesView.py +++ b/odmtools/view/NewFlagValuesView.py @@ -41,6 +41,7 @@ def __init__(self, parent): self.ok_button = wx.Button(bottom_panel, label="OK") self.cancel_button = wx.Button(bottom_panel, label="CANCEL") static_line = wx.StaticLine(bottom_panel) + self.cancel_button.SetDefault() bottom_panel_sizer = wx.BoxSizer(wx.VERTICAL) button_sizer = wx.BoxSizer(wx.HORIZONTAL) From 92104cb4a652572526a6fe01cf09e099e7404388 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 28 Nov 2016 14:27:46 -0700 Subject: [PATCH 088/158] update annotation creation --- odmtools/controller/logicEditTools.py | 3 --- odmtools/odmdata/memory_database.py | 18 +++++++++--------- odmtools/odmservices/edit_service.py | 2 -- odmtools/odmservices/series_service.py | 3 +-- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index 7122515..47613ba 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -60,9 +60,6 @@ def fill_gap(self, gap, fill): Publisher.sendMessage("scroll") - - - def data_gaps(self, value, time_period): self._edit_service.data_gaps(value, time_period) self.refresh_selection() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 0c5a064..403dc04 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -10,6 +10,7 @@ # ODM = SeriesService.ODM from odm2api.ODM2.models import TimeSeriesResultValues as TSRV from odm2api.ODM2.models import setSchema +import pandas as pd logger =logging.getLogger('main') @@ -37,10 +38,10 @@ def __init__(self, taskserver=None): #else: self.taskserver = taskserver + self.annotation_list = pd.DataFrame[], columns =['ResultID', 'ValueDateTime', 'ValueID', 'AnnotationID'] #send in engine - def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") @@ -150,14 +151,15 @@ def chunking(self, data): #break into chunks to get around sqlite's restriction. allowing user to send in only 999 arguments at once #TODO update to work with odm2 + def updateFlag(self, ids, value): - chunks=self.chunking(ids) - for c in chunks: - # add entry in the Timeseriesresultvalueannotations table - self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ - .update({TSRV.qualifier_id: value}, False) + # chunks=self.chunking(ids) + # for c in chunks: + # # add entry in the Timeseriesresultvalueannotations table + # self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ + # .update({TSRV.qualifier_id: value}, False) + self.annotation_list.append(ids) - def delete(self, ids): @@ -243,8 +245,6 @@ def initEditValues(self, seriesID): logger.debug("done loading database") - - def changeSeriesIDs(self, result): """ diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 55f745b..ccaf422 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -236,8 +236,6 @@ def data_gaps(self, value, time_period): self.filtered_dataframe= df[df.index.isin(newdf.drop_duplicates().dropna())] - - def change_value_threshold(self, value, operator): df = self._test_filter_previous() diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 1708972..5e5f2ed 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -113,7 +113,6 @@ def get_variables_by_site_code(self, site_code): .filter(Results.FeatureActionID == FeatureActions.FeatureActionID) .filter(FeatureActions.SamplingFeatureID == SamplingFeatures.SamplingFeatureID) .filter(SamplingFeatures.SamplingFeatureCode == site_code).all() - ] except: var_ids = None @@ -471,7 +470,7 @@ def get_plot_values(self, seriesID, noDataValue, startDate=None, endDate=None): :param endDate: :return: """ - + setSchema(self._session_factory.engine) Values = self.get_values(seriesID) data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] # data = data[data['datavalue'] != noDataValue] From e954952e309e093112477c38c73701da1aaa1e8c Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 28 Nov 2016 14:35:42 -0700 Subject: [PATCH 089/158] update annotations --- .../controller/NewFlagValuesController.py | 5 +- odmtools/odmdata/memory_database.py | 17 +- odmtools/odmdata/series.py | 150 ------------------ odmtools/odmdata/session_factory.py | 57 ------- odmtools/odmservices/series_service.py | 2 +- odmtools/view/NewFlagValuesView.py | 2 +- 6 files changed, 18 insertions(+), 215 deletions(-) delete mode 100644 odmtools/odmdata/series.py delete mode 100644 odmtools/odmdata/session_factory.py diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py index f27bfe9..98a141f 100644 --- a/odmtools/controller/NewFlagValuesController.py +++ b/odmtools/controller/NewFlagValuesController.py @@ -41,6 +41,9 @@ def on_ok(self, event): text = self.text_textbox.GetValue() annotation = self.series_service.create_annotation(code, text) - self.record_service.flag(annotation.AnnotationID) + else: + code = selection.split(':')[0] + annotation = self.series_service.get_annotation_by_code(code) + self.record_service.flag(annotation.AnnotationID) self.on_cancel(event) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index e52f463..987ab55 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -156,11 +156,18 @@ def chunking(self, data): #break into chunks to get around sqlite's restriction. allowing user to send in only 999 arguments at once #TODO update to work with odm2 def updateFlag(self, ids, value): - chunks=self.chunking(ids) - for c in chunks: - # add entry in the Timeseriesresultvalueannotations table - self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ - .update({TSRV.qualifier_id: value}, False) + import pandas as pd + + flags = pd.DataFrame() + if "dates" not in flags.columns: + flags.dates = ids + flags.AnnotationID = value + + # chunks=self.chunking(ids) + # for c in chunks: + # # add entry in the Timeseriesresultvalueannotations table + # self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ + # .update({TSRV.qualifier_id: value}, False) diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py deleted file mode 100644 index a287d85..0000000 --- a/odmtools/odmdata/series.py +++ /dev/null @@ -1,150 +0,0 @@ -from collections import OrderedDict # Requires Python 2.7 >= - -from sqlalchemy import Column, Integer, ForeignKey, String, Float, DateTime -from sqlalchemy.orm import relationship - -from base import Base -from site import Site -from variable import Variable -from method import Method -from source import Source -from quality_control_level import QualityControlLevel - -def copy_series(from_series): - new = Series() - new.site_id = from_series.site_id - new.site_code = from_series.site_code - new.site_name = from_series.site_name - new.variable_id = from_series.variable_id - new.variable_code = from_series.variable_code - new.variable_name = from_series.variable_name - new.speciation = from_series.speciation - new.variable_units_id = from_series.variable_units_id - new.variable_units_name = from_series.variable_units_name - new.sample_medium = from_series.sample_medium - new.value_type = from_series.value_type - new.time_support = from_series.time_support - new.time_units_id = from_series.time_units_id - new.time_units_name = from_series.time_units_name - new.data_type = from_series.data_type - new.general_category = from_series.general_category - new.method_id = from_series.method_id - new.method_description = from_series.method_description - new.source_id = from_series.source_id - new.source_description = from_series.source_description - new.organization = from_series.organization - new.citation = from_series.citation - new.quality_control_level_id = from_series.quality_control_level_id - new.quality_control_level_code = from_series.quality_control_level_code - new.begin_date_time = from_series.begin_date_time - new.begin_date_time_utc = from_series.begin_date_time_utc - new.end_date_time_utc = from_series.end_date_time_utc - new.value_count = from_series.value_count - return new -class Series(Base): - __tablename__ = 'seriescatalog' - - id = Column('SeriesID', Integer, primary_key=True) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - site_code = Column('SiteCode', String) - site_name = Column('SiteName', String) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - variable_code = Column('VariableCode', String) - variable_name = Column('VariableName', String) - - speciation = Column('Speciation', String) - variable_units_id = Column('VariableUnitsID', Integer) - variable_units_name = Column('VariableUnitsName', String) - sample_medium = Column('SampleMedium', String) - value_type = Column('ValueType', String) - time_support = Column('TimeSupport', Float) - time_units_id = Column('TimeUnitsID', Integer) - time_units_name = Column('TimeUnitsName', String) - data_type = Column('DataType', String) - general_category = Column('GeneralCategory', String) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - method_description = Column('MethodDescription', String) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - source_description = Column('SourceDescription', String) - organization = Column('Organization', String) - citation = Column('Citation', String) - quality_control_level_id = Column('QualityControlLevelID', Integer, - ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - quality_control_level_code = Column('QualityControlLevelCode', String) - begin_date_time = Column('BeginDateTime', DateTime) - end_date_time = Column('EndDateTime', DateTime) - begin_date_time_utc = Column('BeginDateTimeUTC', DateTime) - end_date_time_utc = Column('EndDateTimeUTC', DateTime) - value_count = Column('ValueCount', Integer) - - data_values = relationship("DataValue", - primaryjoin="and_(DataValue.site_id == Series.site_id, " - "DataValue.variable_id == Series.variable_id, " - "DataValue.method_id == Series.method_id, " - "DataValue.source_id == Series.source_id, " - "DataValue.quality_control_level_id == Series.quality_control_level_id)", - foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", - order_by="DataValue.local_date_time", - backref="series_service") - - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - - def __repr__(self): - return "" % (self.id, self.site_name, self.variable_code, self.variable_name) - - def __eq__(self, other) : - # return self.__dict__ == other.__dict__ - return [self.id, self.site_id, self.site_code, self.site_name, self.variable_id, self.variable_code, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.quality_control_level_code, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] ==\ - [other.id, other.site_id, other.site_code, other.site_name, other.variable_id, other.variable_code, - other.variable_name, other.speciation, other.variable_units_id, other.variable_units_name, - other.sample_medium, other.value_type, other.time_support, other.time_units_id, other.time_units_name, - other.data_type, other.general_category, other.method_id, other.method_description, - other.source_id, other.source_description, other.organization, other.citation, - other.quality_control_level_id, other.quality_control_level_code, other.begin_date_time, - other.end_date_time, other.begin_date_time_utc, other.end_date_time_utc, other.value_count] - - - def get_table_columns(self): - return self.__table__.columns.keys() - - def list_repr(self): - return [self.id, self.site_code, self.variable_code, self.quality_control_level_code, - self.site_id, self.site_name, self.variable_id, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] - -def returnDict(): - keys = ['SeriesID', 'SiteCode','VariableCode','QualityControlLevelCode', - 'SiteID', 'SiteName', 'VariableID', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_code','variable_code','quality_control_level_code', - 'site_id', 'site_name', 'variable_id', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) - - - diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py deleted file mode 100644 index 8afda48..0000000 --- a/odmtools/odmdata/session_factory.py +++ /dev/null @@ -1,57 +0,0 @@ -# from sqlalchemy import create_engine -# from sqlalchemy.orm import sessionmaker -# -# -# class SessionFactory(): -# def __init__(self, connection_string, echo): -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_size=20) -# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'connect_timeout': 1}) -# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'timeout': 1}) -# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# connect_args={'connect_timeout': 1}) -# -# ''' -# # Removing pool_timeout and max_overflow allowed the tests to pass -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, pool_size=20, max_overflow=0) -# self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) -# self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) -# self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, -# pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) -# ''' -# -# ''' -# # Old code -# class SessionFactory(): -# def __init__(self, connection_string, echo): -# self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, -# #pool_size=20, -# pool_recycle=3600) -# -# # Create session maker -# self.Session = sessionmaker(bind=self.engine) -# -# def get_session(self): -# return self.Session() -# -# def __repr__(self): -# return "" % (self.engine) -# ''' -# -# # Create session maker -# self.Session = sessionmaker(bind=self.engine) -# self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) -# self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) -# self.my_test_Session = sessionmaker(bind=self.my_test_engine) -# -# def get_session(self): -# return self.Session() -# -# def __repr__(self): -# return "" % (self.engine) -# diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index b43b39e..b186c05 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1069,7 +1069,7 @@ def get_quality_code(self): return self.read.getCVs(type="Quality Code") def get_annotation_by_code(self, code): - return self.read.getAnnotations(type=code) + return self.read.getAnnotations(codes=[code])[0] def get_all_annotations(self): return self.read.getAnnotations(type=None) diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py index 2bbfcd3..0c88d21 100644 --- a/odmtools/view/NewFlagValuesView.py +++ b/odmtools/view/NewFlagValuesView.py @@ -14,7 +14,7 @@ def __init__(self, parent): ########################################## annotation_title = wx.StaticText(content_panel, label="Annotation") - self.annotation_combo = wx.ComboBox(content_panel, style=wx.CB_READONLY | wx.CB_SORT) + self.annotation_combo = wx.ComboBox(content_panel, style=wx.CB_READONLY )#| wx.CB_SORT) code_title = wx.StaticText(content_panel, label="Code") self.code_textbox = wx.TextCtrl(content_panel, size=(100, -1)) text_title = wx.StaticText(content_panel, label="Text") From 077c6176381102ee4a92e4ed7dd44c0dafeb9398 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 28 Nov 2016 14:51:24 -0700 Subject: [PATCH 090/158] New flag form expand and collapses, other bug fixes --- .../controller/NewFlagValuesController.py | 11 +- odmtools/gui/wizSave.py | 4 +- .../lib/ObjectListView/ListCtrlPrinter.py | 4 +- odmtools/lib/ObjectListView/OLVEvent.py | 4 +- odmtools/lib/ObjectListView/ObjectListView.py | 10 +- odmtools/view/CustomCollapsiblePanel.py | 130 ++++++++++++++++++ odmtools/view/NewFlagValuesView.py | 34 +++-- 7 files changed, 171 insertions(+), 26 deletions(-) create mode 100644 odmtools/view/CustomCollapsiblePanel.py diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py index f27bfe9..2dcbb9b 100644 --- a/odmtools/controller/NewFlagValuesController.py +++ b/odmtools/controller/NewFlagValuesController.py @@ -9,11 +9,12 @@ def __init__(self, parent, series_service, qualifier_choice, record_service): self.series_service = series_service self.qualifer_choice = qualifier_choice self.record_service = record_service - self.__new_annotation = "[New Annontation]" + self.__new_annotation = "New Annontation" annotations = self.series_service.get_all_annotations() self.append_items_to_annotation(annotations) self.annotation_combo.SetSelection(0) + self.annotation_combo.Append(self.__new_annotation) self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel) self.Bind(wx.EVT_CLOSE, self.on_cancel) @@ -21,7 +22,6 @@ def __init__(self, parent, series_service, qualifier_choice, record_service): self.MakeModal(True) def append_items_to_annotation(self, annotations): - self.annotation_combo.Append(self.__new_annotation) if not isinstance(annotations, list): print "type(annotations) must be list of annotations" return @@ -44,3 +44,10 @@ def on_ok(self, event): self.record_service.flag(annotation.AnnotationID) self.on_cancel(event) + +if __name__ == '__main__': + app = wx.App(False) + controller = NewFlagValuesController(None, None, None, None) + controller.Show() + app.MainLoop() + diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 06bdb1d..0fa65ea 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -356,7 +356,7 @@ def on_wizard_finished(self, event): wx.YES_NO | wx.ICON_QUESTION) if val == 2: logger.info("User selected yes to save a level 0 dataset") - val_2 = wx.MessageBox("This action cannot be undone.\nAre you sure you are sure?\n", + val_2 = wx.MessageBox("This interactive_item cannot be undone.\nAre you sure you are sure?\n", 'Are you REALLY sure?', wx.YES_NO | wx.ICON_QUESTION) if val_2 == 2: @@ -368,7 +368,7 @@ def on_wizard_finished(self, event): if self.pgExisting.pnlExisting.rbAppend.GetValue(): keyword = "append to" - message = "You are about to " + keyword + " an existing series_service,\nthis action cannot be undone.\nWould you like to continue?\n" + message = "You are about to " + keyword + " an existing series_service,\nthis interactive_item cannot be undone.\nWould you like to continue?\n" cont = wx.MessageBox(message, 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) if cont == 2: closeSuccessful = True diff --git a/odmtools/lib/ObjectListView/ListCtrlPrinter.py b/odmtools/lib/ObjectListView/ListCtrlPrinter.py index 35d8164..2e6e3a2 100644 --- a/odmtools/lib/ObjectListView/ListCtrlPrinter.py +++ b/odmtools/lib/ObjectListView/ListCtrlPrinter.py @@ -76,7 +76,7 @@ particular, it configures the printing DC so that its origin and scale are correct. This enables the ``ReportEngine`` to simply render the report without knowing the characteristics of the underlying printer DPI, unprintable region, or the scale of a print -preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like +preview. When The ``ListCtrlPrintout`` encounters some interactive_item that is cannot perform (like actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards to the ``ReportEngine``). @@ -1879,7 +1879,7 @@ def CalculateSlices(self, maxWidth, columnWidths): """ firstColumn = 0 - # If a GroupListView has a column just for the expand/collapse, don't include it + # If a GroupListView has a column just for the is_expanded/collapse, don't include it if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: firstColumn = 1 diff --git a/odmtools/lib/ObjectListView/OLVEvent.py b/odmtools/lib/ObjectListView/OLVEvent.py index a447cb4..500a799 100644 --- a/odmtools/lib/ObjectListView/OLVEvent.py +++ b/odmtools/lib/ObjectListView/OLVEvent.py @@ -229,10 +229,10 @@ def __init__(self, objectListView, groups): class ExpandCollapseEvent(VetoableEvent): """ - The user wants to expand or collapse one or more groups, or has just done so. + The user wants to is_expanded or collapse one or more groups, or has just done so. If the handler calls Veto() for a Expanding or Collapsing event, - the expand/collapse action will be cancelled. + the is_expanded/collapse interactive_item will be cancelled. Calling Veto() has no effect on a Expanded or Collapsed event """ diff --git a/odmtools/lib/ObjectListView/ObjectListView.py b/odmtools/lib/ObjectListView/ObjectListView.py index a74d534..f6f144a 100644 --- a/odmtools/lib/ObjectListView/ObjectListView.py +++ b/odmtools/lib/ObjectListView/ObjectListView.py @@ -161,7 +161,7 @@ class ObjectListView(wx.ListCtrl): this is will also put a HTML version into the clipboard) Left-Arrow, Right-Arrow - [GroupListView only] This will collapse/expand all selected groups. + [GroupListView only] This will collapse/is_expanded all selected groups. * oddRowsBackColor When `useAlternateBackColors` is true, odd numbered rows will have this @@ -2620,7 +2620,7 @@ def __init__(self, *args, **kwargs): * useExpansionColumn If this is True (the default), the expansion/contraction icon will have its - own column at position 0. If this is false, the expand/contract icon will be + own column at position 0. If this is false, the is_expanded/contract icon will be in the first user specified column. This must be set before SetColumns() is called. If it is changed, SetColumns() must be called again. """ @@ -3088,7 +3088,7 @@ def Reveal(self, modelObject): return True # Find which group (if any) the object belongs to, and - # expand it and then try to reveal it again + # is_expanded it and then try to reveal it again for group in self.groups: if not group.isExpanded and modelObject in group.modelObjects: self.Expand(group) @@ -3197,7 +3197,7 @@ def _GetValuesAsMultiList(self, objects): """ Return a list of lists of the string of the aspects of the given objects """ - cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the expand icon columns + cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the is_expanded icon columns objects = [x for x in objects if x is not None and not isinstance(x, ListGroup)] return [[column.GetStringValue(x) for column in cols] for x in objects] @@ -3413,7 +3413,7 @@ class ColumnDefn(object): * isSpaceFilling Is this column a space filler? Space filling columns resize to occupy free space within the listview. As the listview is expanded, space filling columns - expand as well. Conversely, as the control shrinks these columns shrink too. + is_expanded as well. Conversely, as the control shrinks these columns shrink too. Space filling columns can disappear (i.e. have a width of 0) if the control becomes too small. You can set `minimumWidth` to prevent them from diff --git a/odmtools/view/CustomCollapsiblePanel.py b/odmtools/view/CustomCollapsiblePanel.py new file mode 100644 index 0000000..95e3fb4 --- /dev/null +++ b/odmtools/view/CustomCollapsiblePanel.py @@ -0,0 +1,130 @@ +import wx + + +class CustomCollapsiblePanel(wx.Panel): + def __init__(self, parent, title="Sample Title", expand=0, use_combo=False, combo_trigger_item=-1): + wx.Panel.__init__(self, parent) + self.__master_sizer = wx.BoxSizer(wx.VERTICAL) + + self.parent = parent # parent of this panel + self.title = title + self.is_expanded = expand # is_expanded status + self.__using_combo = use_combo + self.trigger_item = combo_trigger_item + + # this will be the main sizer for this panel + self.vbox = wx.BoxSizer(wx.VERTICAL) + + # this sizer contains is_expanded button and title of frame + self.hbox = wx.BoxSizer(wx.VERTICAL) + + # self.interactive_item is the item that is interacted with to is_expanded or collapse the panel + if self.__using_combo: + self.interactive_item = wx.ComboBox(self, style=wx.CB_READONLY, name="interactive_item") + self.interactive_item.Bind(wx.EVT_COMBOBOX, self.on_interactive_item) + else: + self.interactive_item = wx.Button(self, label=title, size=(-1, 18), style=wx.BU_LEFT | wx.STATIC_BORDER, name='interactive_item') + self.interactive_item.Bind(wx.EVT_BUTTON, self.on_interactive_item) + + # self.lbl = wx.StaticText(self, -1, size=(-1, 5), name='cplbl') + + self.hbox.Add(self.interactive_item, 1, wx.EXPAND) + # self.hbox.Add(self.lbl, 0) + + # add to main sizer(vbox) + self.vbox.Add(self.hbox, 0, wx.EXPAND) + + def on_interactive_item(self, event=None): + """ + Case for combo box: If the selected item matches the trigger item then expand, otherwise collapse + Case for button: collapse if expanded and expand if collapsed + :param event: + :return: + """ + if isinstance(self.interactive_item, wx.ComboBox): + if self.interactive_item.GetStringSelection() == self.trigger_item: + self.expand_panel() + else: + self.collapse_panel() + else: + if self.is_expanded: + self.collapse_panel() + else: + self.expand_panel() + + if event: + event.Skip() + + def expand_panel(self): + self.is_expanded = True + self.__redraw_panel() + self.GetTopLevelParent().SetSize((400, 300)) + + def __redraw_panel(self): + for child in self.GetChildren(): + if child.GetName() == "interactive_item": + continue + child.Show(self.is_expanded) + self.parent.Layout() + self.parent.SendSizeEvent() # make scrollbars visible if parent is scrolledWindow and if they are required automatically + # self.lbl.SetFocus() # Remove focus from button when pressed + self.parent.Refresh() + + def collapse_panel(self): + self.is_expanded = False + self.__redraw_panel() + self.GetTopLevelParent().SetSize((400, 150)) + + def finish_layout(self): + + allSizers = [] + childSizer = None + # Get all the sizers containing all the children of this panel + for child in self.GetChildren(): + if child.GetName() == 'interactive_item' or child.GetName() == 'cplbl': + continue + + childSizer = child.GetContainingSizer() + if childSizer != None: + # add the sizer in the list if it's no there. + # can't use set as it changes the order of elements + # this way we can have unique sizer or not repeating ones + if not childSizer in allSizers: + allSizers.append(childSizer) + + # Get root level sizers and add to main sizer name 'vbox' + if len(allSizers): + for sizer in self.getRootSizers(allSizers): + self.vbox.Add(sizer, 0, wx.EXPAND) + else: + print 'children of this panel are not in any sizers. They should be in a sizer/s' + + # When deleting this panel in any case, masterSizer is also getting deleted. we have to create it again + if not isinstance(self.__master_sizer, wx._core.BoxSizer): + self.__master_sizer = wx.BoxSizer(wx.VERTICAL) + + self.__master_sizer.Add(self, 0, wx.EXPAND) + + # Rearrange everything + self.SetSizer(self.vbox) + self.Fit() + self.on_interactive_item() + + def getRootSizers(self, sizerList): + ''' + 'sizerList' contains many sizers and may possible nested sizers or sizers added inside another sizers. + This function process the list and returns only root level sizers. + We'll add only root level sizers to main sizer of this class name 'vbox'. + ''' + finalList = sizerList[:] + copyList = sizerList[:] + + for sizer in copyList: + if len(sizer.GetChildren()): + for child in sizer.GetChildren(): + if child.GetClassName() == 'wxSizerItem': + try: + finalList.remove(child.GetSizer()) + except: + pass + return finalList diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py index 2bbfcd3..63e42a9 100644 --- a/odmtools/view/NewFlagValuesView.py +++ b/odmtools/view/NewFlagValuesView.py @@ -1,30 +1,34 @@ import wx +from odmtools.view.CustomCollapsiblePanel import CustomCollapsiblePanel +from wx.lib.scrolledpanel import ScrolledPanel class NewFlagValuesView(wx.Frame): def __init__(self, parent): - wx.Frame.__init__(self, parent, title="Flag Values", style=wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + wx.Frame.__init__(self, parent, title="Flag Values", style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) panel = wx.Panel(self) - content_panel = wx.Panel(panel) + content_panel = ScrolledPanel(panel) bottom_panel = wx.Panel(panel) ########################################## # CONTENT PANEL ########################################## + content_panel.SetupScrolling() annotation_title = wx.StaticText(content_panel, label="Annotation") - self.annotation_combo = wx.ComboBox(content_panel, style=wx.CB_READONLY | wx.CB_SORT) - code_title = wx.StaticText(content_panel, label="Code") - self.code_textbox = wx.TextCtrl(content_panel, size=(100, -1)) - text_title = wx.StaticText(content_panel, label="Text") - self.text_textbox = wx.TextCtrl(content_panel) - link_text = wx.StaticText(content_panel, label="Link") - self.link_textbox = wx.TextCtrl(content_panel) + collapsible_panel = CustomCollapsiblePanel(content_panel, title="Panel 1", expand=0, use_combo=True, combo_trigger_item="New Annontation") + self.annotation_combo = collapsible_panel.interactive_item + code_title = wx.StaticText(collapsible_panel, label="Code") + self.code_textbox = wx.TextCtrl(collapsible_panel, size=(100, -1)) + text_title = wx.StaticText(collapsible_panel, label="Text") + self.text_textbox = wx.TextCtrl(collapsible_panel) + link_text = wx.StaticText(collapsible_panel, label="Link") + self.link_textbox = wx.TextCtrl(collapsible_panel) + + sizer = wx.BoxSizer(wx.VERTICAL) content_panel_sizer = wx.BoxSizer(wx.VERTICAL) - content_panel_sizer.Add(annotation_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) - content_panel_sizer.Add(self.annotation_combo, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) content_panel_sizer.Add(code_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) content_panel_sizer.Add(self.code_textbox, 0, wx.LEFT | wx.RIGHT, 10) content_panel_sizer.Add(text_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) @@ -32,7 +36,11 @@ def __init__(self, parent): content_panel_sizer.Add(link_text, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) content_panel_sizer.Add(self.link_textbox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) - content_panel.SetSizer(content_panel_sizer) + collapsible_panel.finish_layout() + + sizer.Add(annotation_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + sizer.Add(collapsible_panel, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + content_panel.SetSizer(sizer) ########################################## # BOTTOM PANEL @@ -60,7 +68,7 @@ def __init__(self, parent): panel.SetSizer(main_sizer) main_sizer.Fit(self) - self.SetSize((400, 300)) + self.SetSize((400, 150)) From e86eefe8418f6e31c349536023081ddb6ff93bed Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 28 Nov 2016 17:12:36 -0700 Subject: [PATCH 091/158] create a full list of annotations --- odmtools/odmdata/memory_database.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index eb827be..a921141 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -38,7 +38,8 @@ def __init__(self, taskserver=None): #else: self.taskserver = taskserver - self.annotation_list = pd.DataFrame[], columns =['ResultID', 'ValueDateTime', 'ValueID', 'AnnotationID'] + self.annotation_list = pd.DataFrame() + #self.annotation_list = pd.DataFrame() columns =['ResultID', 'ValueDateTime', 'ValueID', 'AnnotationID') #send in engine @@ -154,18 +155,24 @@ def chunking(self, data): def updateFlag(self, ids, value): - flags = pd.DataFrame() - if "dates" not in flags.columns: - flags.dates = ids - flags.AnnotationID = value + flags = pd.DataFrame(columns = ['AnnotationID', 'DateTime', 'ResultID', 'ValueID']) + flags["DateTime"] = ids + flags["AnnotationID"] = value + flags["ResultID"] = self.series.ResultID + flags["ValueID"] = None + + + #what if the column already exists # chunks=self.chunking(ids) # for c in chunks: # # add entry in the Timeseriesresultvalueannotations table # self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ # .update({TSRV.qualifier_id: value}, False) - - #self.annotation_list.append(ids) + + frames = [self.annotation_list, flags] + self.annotation_list=pd.concat(frames) + print self.annotation_list From 2c26bb19119ff2af948cf215bd72261ba9a7505a Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 29 Nov 2016 12:50:48 -0700 Subject: [PATCH 092/158] Fixed bug on the summary in the save wiz --- odmtools/gui/wizSave.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 0fa65ea..db3f01f 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -184,32 +184,21 @@ def __init__(self, parent, title, series_service): def fill_summary(self): sampling_feature, variable, method, source, processing_level = self.parent.get_metadata() - ## self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, "Code: "+ str(QCL.code)) - - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(sampling_feature.SamplingFeatureCode)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(sampling_feature.SamplingFeatureName)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.vc, 'Code: ' + str(variable.VariableCode)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.vn, 'Name: ' + str(variable.VariableNameCV)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vu, 'Units: ' + str(variable.variable_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vsm, 'Sample Medium: ' + str(variable.sample_medium)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vvt, 'Value Type: ' + str(variable.value_type)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vts, 'Time Support: ' + str(variable.time_support)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vtu, 'Time Units: ' + str(variable.time_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vdt, 'Data Type: ' + str(variable.data_type)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vgc, - 'General Category: ' + str(variable.general_category)) - - self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.description)) - - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(source.organization)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(source.description)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(source.citation)) - - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.definition)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(processing_level.explanation)) + + self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.MethodDescription)) + + # self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(source.organization)) + # self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(source.description)) + # self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(source.citation)) + + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.ProcessingLevelCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.Definition)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(processing_level.Explanation)) self.panel.treeSummary.ExpandAll() From 6c0137a2c6a05868a92d990aba04d60b9e2f237f Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 30 Nov 2016 12:54:53 -0700 Subject: [PATCH 093/158] Added the action form to the wizard and created a get all affiliations --- odmtools/controller/WizardActionController.py | 25 ++++++++ odmtools/gui/wizSave.py | 14 ++++- odmtools/odmservices/series_service.py | 5 +- odmtools/view/WizardActionView.py | 60 +++++++++++++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 odmtools/controller/WizardActionController.py create mode 100644 odmtools/view/WizardActionView.py diff --git a/odmtools/controller/WizardActionController.py b/odmtools/controller/WizardActionController.py new file mode 100644 index 0000000..a09b5ef --- /dev/null +++ b/odmtools/controller/WizardActionController.py @@ -0,0 +1,25 @@ +import wx +from odmtools.view.WizardActionView import WizardActionView +from wx.wizard import WizardPageSimple + + +class WizardActionController(WizardPageSimple): + def __init__(self, parent, affiliations): + WizardPageSimple.__init__(self, parent) + self.action_view = WizardActionView(self) + self.affiliations = affiliations + + master_sizer = wx.BoxSizer(wx.VERTICAL) + master_sizer.Add(self.action_view, 1, wx.EXPAND | wx.RIGHT, 0) + self.SetSizer(master_sizer) + + if self.affiliations is None: + return + # Populate table with the affiliations + + +if __name__ == '__main__': + app = wx.App(False) + controller = WizardActionController(None, None) + controller.Show() + app.MainLoop() \ No newline at end of file diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index db3f01f..8e537f6 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -207,6 +207,7 @@ def fill_summary(self): from odmtools.controller.WizardMethodController import WizardMethodController from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController from odmtools.controller.WizardVariableController import WizardVariableController +from odmtools.controller.WizardActionController import WizardActionController class wizSave(wx.wizard.Wizard): @@ -269,6 +270,11 @@ def __init__(self, parent, service_manager, record_service): current_variable=self.currSeries.VariableObj) self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.FeatureActionObj.SamplingFeatureObj) + + affiliations = self.series_service.get_all_affiliations() + + self.action_page = WizardActionController(self, affiliations=affiliations) + self.pgSummary = SummaryPage(self, "Summary", self.series_service) self.FitToPage(self.pgIntro) @@ -277,7 +283,6 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro.SetNext(self.pgSummary) self.pgSummary.SetPrev(self.pgIntro) - #SaveAs Pages self.pgMethod.SetPrev(self.pgIntro) self.pgMethod.SetNext(self.pgQCL) @@ -286,7 +291,10 @@ def __init__(self, parent, service_manager, record_service): self.pgQCL.SetNext(self.pgVariable) self.pgVariable.SetPrev(self.pgQCL) - self.pgVariable.SetNext(self.pgSummary) + self.pgVariable.SetNext(self.action_page) + + self.action_page.SetPrev(self.pgVariable) + self.action_page.SetNext(self.pgSummary) #Save existing page self.pgExisting.SetPrev(self.pgIntro) @@ -308,7 +316,7 @@ def on_page_changing(self, event): elif self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): self.pgIntro.SetNext(self.pgMethod) - self.pgSummary.SetPrev(self.pgVariable) + self.pgSummary.SetPrev(self.action_page) else: self.pgIntro.SetNext(self.pgExisting) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index c43d19c..46f313e 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1074,4 +1074,7 @@ def get_all_annotations(self): return self.read.getAnnotations(type=None) def get_aggregation_statistic(self): - return self.read.getCVs(type="aggregationstatistic") \ No newline at end of file + return self.read.getCVs(type="aggregationstatistic") + + def get_all_affiliations(self): + return self.read.getAffiliations(ids=None, personfirst=None, personlast=None, orgcode=None) \ No newline at end of file diff --git a/odmtools/view/WizardActionView.py b/odmtools/view/WizardActionView.py new file mode 100644 index 0000000..7c241ba --- /dev/null +++ b/odmtools/view/WizardActionView.py @@ -0,0 +1,60 @@ +import wx +from odmtools.view.CustomListCtrl import CustomListCtrl +import wx.lib.scrolledpanel + + +class WizardActionView(wx.lib.scrolledpanel.ScrolledPanel): + def __init__(self, parent): + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) + + # Header + header_text = wx.StaticText(self, label="Action") + static_line = wx.StaticLine(self, size=(-1, 12)) + + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + # REQUIRED FIELDS + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + affiliations_text = wx.StaticText(self, label="Affiliations") + self.affiliations_table = CustomListCtrl(self) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(affiliations_text, 0, wx.EXPAND) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(self.affiliations_table, 1, wx.EXPAND) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + # OPTIONAL FIELDS + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + action_file_link_text = wx.StaticText(self, label="Action File Link") + action_file_link_text_box = wx.TextCtrl(self) + description_text = wx.StaticText(self, label="Description") + description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + role_description_text = wx.StaticText(self, label="Role Description") + role_description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + + flex_grid_sizer = wx.FlexGridSizer(rows=3, cols=2, vgap=9, hgap=25) + + flex_grid_sizer.AddMany([(action_file_link_text), (action_file_link_text_box, 1, wx.EXPAND), + (description_text), (description_text_box, 1, wx.EXPAND), + (role_description_text), (role_description_text_box, 1, wx.EXPAND) + ]) + + flex_grid_sizer.AddGrowableRow(1, 1) + flex_grid_sizer.AddGrowableCol(1, 1) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(flex_grid_sizer, 1, wx.EXPAND) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + master_sizer = wx.BoxSizer(wx.VERTICAL) + master_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + master_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + master_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 5) + master_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 5) + + self.SetSizer(master_sizer) + # master_sizer.Fit(self) From dcbda2b9a77861c48e2ded6648efd112c2362a13 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 1 Dec 2016 12:27:29 -0700 Subject: [PATCH 094/158] Added affiliations to the action form and updated the summary page in the save wizard --- odmtools/controller/WizardActionController.py | 27 ++++++++++++++++++ odmtools/gui/pageSummary.py | 28 ++++++++----------- odmtools/gui/wizSave.py | 15 +++++----- odmtools/view/WizardActionView.py | 8 ++++-- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/odmtools/controller/WizardActionController.py b/odmtools/controller/WizardActionController.py index a09b5ef..37678c2 100644 --- a/odmtools/controller/WizardActionController.py +++ b/odmtools/controller/WizardActionController.py @@ -1,6 +1,7 @@ import wx from odmtools.view.WizardActionView import WizardActionView from wx.wizard import WizardPageSimple +from odm2api.ODM2.models import Affiliations class WizardActionController(WizardPageSimple): @@ -16,6 +17,32 @@ def __init__(self, parent, affiliations): if self.affiliations is None: return # Populate table with the affiliations + self.populate_affiliations_table() + + def populate_affiliations_table(self): + """ + self.affiliations must be a list affiliations + :return: + """ + if not isinstance(self.affiliations, list): + return + if not len(self.affiliations) and not isinstance(self.affiliations[0], Affiliations): + return + + data = [] + for affiliation in self.affiliations: + data.append([ + affiliation.PersonObj.PersonFirstName + " " + affiliation.PersonObj.PersonLastName, + affiliation.OrganizationObj.OrganizationName + ]) + + columns = ["Person", "Organization"] + self.action_view.affiliations_table.set_columns(columns) + self.action_view.affiliations_table.set_table_content(data) + + def get_action(self): + index = self.action_view.affiliations_table.GetFirstSelected() + return self.affiliations[index] if __name__ == '__main__': diff --git a/odmtools/gui/pageSummary.py b/odmtools/gui/pageSummary.py index a9a9be2..c7b3b0e 100644 --- a/odmtools/gui/pageSummary.py +++ b/odmtools/gui/pageSummary.py @@ -31,8 +31,6 @@ def _init_ctrls(self, prnt): parent=self, pos=wx.Point(0, 0), size=wx.Size(423, 319), style=wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT) - - self._init_sizers() def __init__(self, parent, id, size, style, name, ss, pos= (0,0)): @@ -40,21 +38,19 @@ def __init__(self, parent, id, size, style, name, ss, pos= (0,0)): self._init_ctrls(parent) - -class MyTree(wx.TreeCtrl): - +class MyTree(wx.TreeCtrl): # Who's tree is this? Who said it was theirs? def __init__(self, parent, id, pos, size, style): wx.TreeCtrl.__init__(self, parent, id, pos, size, style) self.root = self.AddRoot('Series') - self.s = self.AppendItem(self.root, 'Site') + self.site = self.AppendItem(self.root, 'Site') self.v = self.AppendItem(self.root, 'Variable') self.m = self.AppendItem(self.root, 'Method') - self.so= self.AppendItem(self.root, 'Source') - self.q = self.AppendItem(self.root, 'Quality Control Level') + self.action = self.AppendItem(self.root, 'Action') + self.processing_level = self.AppendItem(self.root, 'Processing Level') - self.sc=self.AppendItem(self.s, 'Code: ') - self.sn=self.AppendItem(self.s, 'Name: ') + self.sc=self.AppendItem(self.site, 'Code: ') + self.sn=self.AppendItem(self.site, 'Name: ') self.vc=self.AppendItem(self.v, 'Code: ') self.vn=self.AppendItem(self.v, 'Name: ') @@ -68,11 +64,11 @@ def __init__(self, parent, id, pos, size, style): self.md=self.AppendItem(self.m, 'Description: ') - self.soo=self.AppendItem(self.so, 'Organization: ') - self.sod=self.AppendItem(self.so, 'Description: ') - self.soc=self.AppendItem(self.so, 'Citation: ') + self.soc=self.AppendItem(self.action, 'Person: ') + self.soo=self.AppendItem(self.action, 'Organization: ') + self.sod=self.AppendItem(self.action, 'Description: ') - self.qc=self.AppendItem(self.q, 'Code: ') - self.qd=self.AppendItem(self.q, 'Definition: ') - self.qe=self.AppendItem(self.q, 'Explanation: ') + self.qc=self.AppendItem(self.processing_level, 'Code: ') + self.qd=self.AppendItem(self.processing_level, 'Definition: ') + self.qe=self.AppendItem(self.processing_level, 'Explanation: ') diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 8e537f6..4513c7f 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -182,7 +182,7 @@ def __init__(self, parent, title, series_service): def fill_summary(self): - sampling_feature, variable, method, source, processing_level = self.parent.get_metadata() + sampling_feature, variable, method, action, processing_level = self.parent.get_metadata() self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(sampling_feature.SamplingFeatureCode)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(sampling_feature.SamplingFeatureName)) @@ -192,9 +192,9 @@ def fill_summary(self): self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.MethodDescription)) - # self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(source.organization)) - # self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(source.description)) - # self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(source.citation)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Person: ' + str(action.PersonObj.PersonFirstName + " " + action.PersonObj.PersonLastName)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(action.OrganizationObj.OrganizationName)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(action.OrganizationObj.OrganizationDescription)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.ProcessingLevelCode)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.Definition)) @@ -228,13 +228,14 @@ def get_metadata(self): method = None variable = None processing_level = None - source = None + action = None if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): logger.debug("SaveAs") method = self.pgMethod.getMethod() processing_level = self.pgQCL.get_processing_level() variable = self.pgVariable.get_variable() + action = self.action_page.get_action() elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): logger.debug("Save") method = self.currSeries.FeatureActionObj.ActionObj.MethodObj @@ -246,8 +247,8 @@ def get_metadata(self): site = self.currSeries.FeatureActionObj.SamplingFeatureObj # source = self.currSeries.source logger.debug("site: %s, variable: %s, method: %s, source: %s, processing_level: %s" % ( - str(site), str(variable), str(method), str(source), str(processing_level))) - return site, variable, method, source, processing_level + str(site), str(variable), str(method), str(action), str(processing_level))) + return site, variable, method, action, processing_level def __init__(self, parent, service_manager, record_service): self._init_ctrls(parent) diff --git a/odmtools/view/WizardActionView.py b/odmtools/view/WizardActionView.py index 7c241ba..154ccaf 100644 --- a/odmtools/view/WizardActionView.py +++ b/odmtools/view/WizardActionView.py @@ -15,10 +15,13 @@ def __init__(self, parent): header_text.SetFont(font) # REQUIRED FIELDS - required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), + orient=wx.VERTICAL) affiliations_text = wx.StaticText(self, label="Affiliations") self.affiliations_table = CustomListCtrl(self) + self.affiliations_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + row_sizer = wx.BoxSizer(wx.HORIZONTAL) row_sizer.Add(affiliations_text, 0, wx.EXPAND) required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) @@ -28,7 +31,8 @@ def __init__(self, parent): required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) # OPTIONAL FIELDS - optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), + orient=wx.VERTICAL) action_file_link_text = wx.StaticText(self, label="Action File Link") action_file_link_text_box = wx.TextCtrl(self) description_text = wx.StaticText(self, label="Description") From 8222b081c2d0bc561a9904a933b2e15cb7be37a2 Mon Sep 17 00:00:00 2001 From: sreeder Date: Thu, 1 Dec 2016 16:11:06 -0700 Subject: [PATCH 095/158] add functionality for basic upsert --- odmtools/odmdata/memory_database.py | 3 +- odmtools/odmservices/series_service.py | 353 +++++++------------------ odmtools/odmservices/to_sql_newrows.py | 193 ++++++++++++++ 3 files changed, 287 insertions(+), 262 deletions(-) create mode 100644 odmtools/odmservices/to_sql_newrows.py diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index a921141..6947184 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -169,7 +169,7 @@ def updateFlag(self, ids, value): # # add entry in the Timeseriesresultvalueannotations table # self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ # .update({TSRV.qualifier_id: value}, False) - + frames = [self.annotation_list, flags] self.annotation_list=pd.concat(frames) print self.annotation_list @@ -207,6 +207,7 @@ def addPoints(self, points): self.mem_service._session.execute(stmt, vals) + def stopEdit(self): self.editLoaded = False self.df = None diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 46f313e..5c9c5d3 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -4,6 +4,7 @@ from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * +from odmtools.odmservices import to_sql_newrows import datetime from odmtools.common.logger import LoggerTool import pandas as pd @@ -543,11 +544,13 @@ def get_all_plot_values(self): # self._edit_session.add_all(merged_dv_list) # self._edit_session.commit() # -# ##################### -# # -# #Create functions -# # -# ##################### + +##################### +# +#Create functions +# +##################### + # def save_series(self, series, dvs): # """ Save to an Existing Series # :param series: @@ -596,7 +599,15 @@ def get_all_plot_values(self): # logger.info("A new series was added to the database, series id: "+str(series.id)) # return True # - def save_values(self, values): + def insert_annotations(self, annotations): + annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) + + def upsert_values(self, values): + newvals= to_sql_newrows(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + filter_categorical_col= "resultdatetime" ) + pass + + def insert_values(self, values): """ :param values: pandas dataframe @@ -644,263 +655,12 @@ def create_method(self, description, link): # self._edit_session.commit() # return meth # # - def create_variable_by_var(self, var): - try: - return self.create.createVariable(var) - except: - return None - # """ - # - # :param var: Variable Object - # :return: - # """ - # try: - # self._edit_session.add(var) - # self._edit_session.commit() - # return var - # except: - # return None -# - def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): - """ -# -# :param code: -# :param name: -# :param speciation: -# :param variable_unit_id: -# :param sample_medium: -# :param value_type: -# :param is_regular: -# :param time_support: -# :param time_unit_id: -# :param data_type: -# :param general_category: -# :param no_data_value: -# :return: -# """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - self.create.createVariable(var) -# self._edit_session.add(var) -# self._edit_session.commit() -# return var -# - def create_qcl(self, code, definition, explanation): - """ - :param code: - :param definition: - :param explanation: - :return: - """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - - return self.create.createProcessingLevel(qcl) - # self._edit_session.add(qcl) - # self._edit_session.commit() - # return qcl -# -# - def create_qualifier_by_qual(self, qualifier): - # self._edit_session.add(qualifier) - # self._edit_session.commit() - # return qualifier - return self.create.createAnnotations(qualifier) -# - def create_qualifier(self, code, description): - # """ - # - # :param code: - # :param description: - # :return: - # """ - qual = Qualifier() - qual.code = code - qual.description = description - # - # return self.create_qualifier_by_qual(qual) - return self.create.createAnnotations(qual); -# -# ##################### -# # -# # Delete functions -# # -# ##################### -# -# def delete_series(self, series): -# """ -# -# :param series: -# :return: -# """ -# try: -# self.delete_values_by_series(series) -# -# delete_series = self._edit_session.merge(series) -# self._edit_session.delete(delete_series) -# self._edit_session.commit() -# except Exception as e: -# message = "series was not successfully deleted: %s" % e -# print message -# logger.error(message) -# raise e -# -# - def delete_values_by_series(self, series, startdate = None): - """ - :param series: - :return: - """ - #todo stephanie: add startdate stuff - try: - self.delete.deleteTSRValues(ids = [series.id]) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex -# try: -# q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, -# variable_id = series.variable_id, -# method_id = series.method_id, -# source_id = series.source_id, -# quality_control_level_id = series.quality_control_level_id) -# if startdate is not None: -# #start date indicates what day you should start deleting values. the values will delete to the end of the series -# return q.filter(DataValue.local_date_time >= startdate).delete() -# else: -# return q.delete() -# -# except Exception as ex: -# message = "Values were not successfully deleted: %s" % ex -# print message -# logger.error(message) -# raise ex -# - def delete_dvs(self, id_list): - """ - - :param id_list: list of datetimes - :return: - """ - try: - self.delete.deleteTSRValues(dates = id_list) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex - - -# ##################### -# # -# #Exist functions -# # -# ##################### -# -# -# def series_exists(self, series): -# """ -# -# :param series: -# :return: -# """ -# return self.series_exists_quint( -# series.site_id, -# series.variable_id, -# series.method_id, -# series.source_id, -# series.quality_control_level_id -# ) -# -# def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): -# """ -# -# :param site_id: -# :param var_id: -# :param method_id: -# :param source_id: -# :param qcl_id: -# :return: -# """ -# try: -# result = self._edit_session.query(Series).filter_by( -# site_id=site_id, -# variable_id=var_id, -# method_id=method_id, -# source_id=source_id, -# quality_control_level_id=qcl_id -# ).one() -# -# return True -# except: -# return False -# -# def qcl_exists(self, q): -# """ -# -# :param q: -# :return: -# """ -# try: -# result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() -# return True -# except: -# -# return False -# -# def method_exists(self, m): -# """ -# -# :param m: -# :return: -# """ -# try: -# result = self._edit_session.query(Method).filter_by(description=m.description).one() -# return True -# except: -# return False -# -# def variable_exists(self, v): -# """ -# -# :param v: -# :return: -# """ -# try: -# result = self._edit_session.query(Variable).filter_by(code=v.code, -# name=v.name, speciation=v.speciation, -# variable_unit_id=v.variable_unit_id, -# sample_medium=v.sample_medium, -# value_type=v.value_type, is_regular=v.is_regular, -# time_support=v.time_support, -# time_unit_id=v.time_unit_id, data_type=v.data_type, -# general_category=v.general_category, -# no_data_value=v.no_data_value).one() -# return result -# except: -# return None def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): + #create a result and an Action object of type derivation """ series_service -> Result in ODM2 :param data_values: @@ -912,7 +672,7 @@ def create_new_series(self, data_values, site_id, variable_id, method_id, source :return: """ self.update_dvs(data_values) - series = Series() + series = Results() series.site_id = site_id series.variable_id = variable_id series.method_id = method_id @@ -1017,7 +777,6 @@ def create_annotation(self, code, text, link=None): return self.create_annotation_by_anno(annotation) - def get_vertical_datum_cvs(self): return self.read.getCVs(type="Elevation Datum") @@ -1077,4 +836,76 @@ def get_aggregation_statistic(self): return self.read.getCVs(type="aggregationstatistic") def get_all_affiliations(self): - return self.read.getAffiliations(ids=None, personfirst=None, personlast=None, orgcode=None) \ No newline at end of file + return self.read.getAffiliations(ids=None, personfirst=None, personlast=None, orgcode=None) + + # ##################### + # # + # # Delete functions + # # + # ##################### + # + # def delete_series(self, series): + # """ + # + # :param series: + # :return: + # """ + # try: + # self.delete_values_by_series(series) + # + # delete_series = self._edit_session.merge(series) + # self._edit_session.delete(delete_series) + # self._edit_session.commit() + # except Exception as e: + # message = "series was not successfully deleted: %s" % e + # print message + # logger.error(message) + # raise e + # + # + def delete_values_by_series(self, series, startdate=None): + """ + + :param series: + :return: + """ + # todo stephanie: add startdate stuff + try: + self.delete.deleteTSRValues(ids=[series.id]) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex + # try: + # q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, + # variable_id = series.variable_id, + # method_id = series.method_id, + # source_id = series.source_id, + # quality_control_level_id = series.quality_control_level_id) + # if startdate is not None: + # #start date indicates what day you should start deleting values. the values will delete to the end of the series + # return q.filter(DataValue.local_date_time >= startdate).delete() + # else: + # return q.delete() + # + # except Exception as ex: + # message = "Values were not successfully deleted: %s" % ex + # print message + # logger.error(message) + # raise ex + # + + def delete_dvs(self, id_list): + """ + + :param id_list: list of datetimes + :return: + """ + try: + self.delete.deleteTSRValues(dates=id_list) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex diff --git a/odmtools/odmservices/to_sql_newrows.py b/odmtools/odmservices/to_sql_newrows.py new file mode 100644 index 0000000..0133a43 --- /dev/null +++ b/odmtools/odmservices/to_sql_newrows.py @@ -0,0 +1,193 @@ +import os +import sys +import time +import pandas as pd +import numpy as np +from sqlalchemy import create_engine +import threading +from timeit import default_timer as timer + +os.path.dirname(os.path.abspath(__file__)) + + +def clean_df_db_dups(df, tablename, engine, dup_cols=[], + filter_continuous_col=None, filter_categorical_col=None): + """ + Remove rows from a dataframe that already exist in a database + Required: + df : dataframe to remove duplicate rows from + engine: SQLAlchemy engine object + tablename: tablename to check duplicates in + dup_cols: list or tuple of column names to check for duplicate row values + Optional: + filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter + can be either a datetime, int, or float data type + useful for restricting the database table size to check + filter_categorical_col : the name of the categorical data column for Where = value check + Creates an "IN ()" check on the unique values in this column + Returns + Unique list of values from dataframe compared to database table + """ + args = 'SELECT %s FROM %s' % (', '.join(['"{0}"'.format(col) for col in dup_cols]), tablename) + args_contin_filter, args_cat_filter = None, None + if filter_continuous_col is not None: + if df[filter_continuous_col].dtype == 'datetime64[ns]': + args_contin_filter = """ "%s" BETWEEN Convert(datetime, '%s') + AND Convert(datetime, '%s')""" % (filter_continuous_col, + df[filter_continuous_col].min(), + df[filter_continuous_col].max()) + + if filter_categorical_col is not None: + args_cat_filter = ' "%s" in(%s)' % (filter_categorical_col, + ', '.join(["'{0}'".format(value) for value in + df[filter_categorical_col].unique()])) + + if args_contin_filter and args_cat_filter: + args += ' Where ' + args_contin_filter + ' AND' + args_cat_filter + elif args_contin_filter: + args += ' Where ' + args_contin_filter + elif args_cat_filter: + args += ' Where ' + args_cat_filter + + df.drop_duplicates(dup_cols, keep='last', inplace=True) + df = pd.merge(df, pd.read_sql(args, engine), how='left', on=dup_cols, indicator=True) + df = df[df['_merge'] == 'left_only'] + df.drop(['_merge'], axis=1, inplace=True) + return df + + +def to_sql_newrows(df, pool_size, *args, **kargs): + """ + Extend the Python pandas to_sql() method to thread database insertion + + Required: + df : pandas dataframe to insert new rows into a database table + POOL_SIZE : your sqlalchemy max connection pool size. Set < your db connection limit. + Example where this matters: your cloud DB has a connection limit. + *args: + Pandas to_sql() arguments. + + Required arguments are: + tablename : Database table name to write results to + engine : SqlAlchemy engine + + Optional arguments are: + 'if_exists' : 'append' or 'replace'. If table already exists, use append. + 'index' : True or False. True if you want to write index values to the db. + + + Credits for intial threading code: + http://techyoubaji.blogspot.com/2015/10/speed-up-pandas-tosql-with.html + """ + + CHUNKSIZE = 1000 + INITIAL_CHUNK = 100 + if len(df) > CHUNKSIZE: + # write the initial chunk to the database if df is bigger than chunksize + df.iloc[:INITIAL_CHUNK, :].to_sql(*args, **kargs) + else: + # if df is smaller than chunksize, just write it to the db now + df.to_sql(*args, **kargs) + + workers, i = [], 0 + + for i in range((df.shape[0] - INITIAL_CHUNK) / CHUNKSIZE): + t = threading.Thread( + target=lambda: df.iloc[INITIAL_CHUNK + i * CHUNKSIZE:INITIAL_CHUNK + (i + 1) * CHUNKSIZE].to_sql(*args, + **kargs)) + t.start() + workers.append(t) + + df.iloc[INITIAL_CHUNK + (i + 1) * CHUNKSIZE:, :].to_sql(*args, **kargs) + [t.join() for t in workers] + + +def setup(engine, tablename): + engine.execute("""DROP TABLE IF EXISTS "%s" """ % (tablename)) + + engine.execute("""CREATE TABLE "%s" ( + "A" INTEGER, + "B" INTEGER, + "C" INTEGER, + "D" INTEGER, + CONSTRAINT pk_A_B PRIMARY KEY ("A","B")) + """ % (tablename)) + + +if __name__ == '__main__': + DB_TYPE = 'postgresql' + DB_DRIVER = 'psycopg2' + DB_USER = 'admin' + DB_PASS = 'password' + DB_HOST = 'localhost' + DB_PORT = '5432' + DB_NAME = 'pandas_upsert' + POOL_SIZE = 50 + TABLENAME = 'test_upsert' + SQLALCHEMY_DATABASE_URI = '%s+%s://%s:%s@%s:%s/%s' % (DB_TYPE, DB_DRIVER, DB_USER, + DB_PASS, DB_HOST, DB_PORT, DB_NAME) + ENGINE = create_engine( + SQLALCHEMY_DATABASE_URI, pool_size=POOL_SIZE, max_overflow=0) + + print 'setting up db' + setup(ENGINE, TABLENAME) + + try: + i = 0 + prev = timer() + start = timer() + for i in range(10): + print 'running test %s' % (str(i)) + df = pd.DataFrame( + np.random.randint(0, 500, size=(100000, 4)), columns=list('ABCD')) + df = clean_df_db_dups(df, TABLENAME, ENGINE, dup_cols=['A', 'B']) + print 'row count after drop db duplicates is now : %s' % (df.shape[0]) + df.to_sql(TABLENAME, ENGINE, if_exists='append', index=False) + end = timer() + elapsed_time = end - prev + prev = timer() + print 'completed loop in %s sec!' % (elapsed_time) + i += 1 + end = timer() + elapsed_time = end - start + print 'completed singlethread insert loops in %s sec!' % (elapsed_time) + inserted = pd.read_sql('SELECT count("A") from %s' % (TABLENAME), ENGINE) + print 'inserted %s new rows into database!' % (inserted.iloc[0]['count']) + + print '\n setting up db' + setup(ENGINE, TABLENAME) + print '\n' + + i = 0 + prev = timer() + start = timer() + for i in range(10): + print 'running test %s' % (str(i)) + df = pd.DataFrame( + np.random.randint(0, 500, size=(100000, 4)), columns=list('ABCD')) + df.drop_duplicates(['A', 'B'], keep='last', inplace=True) + df.to_sql('temp', ENGINE, if_exists='replace', index=False) + connection = ENGINE.connect() + args1 = """ INSERT INTO "test_upsert" + SELECT * FROM + (SELECT a.* + FROM "temp" a LEFT OUTER JOIN "test_upsert" b + ON (a."A" = b."A" and a."B"=b."B") + WHERE b."A" is null) b""" + result = connection.execute(args1) + args2 = """ DROP Table If Exists "temp" """ + connection.execute(args2) + connection.close() + end = timer() + elapsed_time = end - prev + prev = timer() + print 'completed loop in %s sec!' % (elapsed_time) + i += 1 + end = timer() + elapsed_time = end - start + print 'completed staging insert loops in %s sec!' % (elapsed_time) + inserted = pd.read_sql('SELECT count("A") from %s' % (TABLENAME), ENGINE) + print 'inserted %s new rows into database!' % (inserted.iloc[0]['count']) + + except KeyboardInterrupt: + print("Interrupted... exiting...") \ No newline at end of file From b208dfb326692416d11d99fbeb9f01b4bbcf3c48 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Fri, 2 Dec 2016 12:47:05 -0700 Subject: [PATCH 096/158] In the save wizard, auto checks the existing, and highlights the item in the table --- odmtools/controller/WizardMethodController.py | 48 +++++++++++-------- .../WizardProcessLevelController.py | 32 ++++++++++--- odmtools/gui/wizSave.py | 15 +++--- odmtools/odmservices/series_service.py | 2 +- odmtools/view/WizardMethodView.py | 3 +- odmtools/view/WizardProcessLevelView.py | 2 + 6 files changed, 68 insertions(+), 34 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 88eaee5..113767e 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -5,10 +5,11 @@ class WizardMethodController(WizardPageSimple): - def __init__(self, parent, series_service): + def __init__(self, parent, series_service, current_method=None): WizardPageSimple.__init__(self, parent) main_sizer = wx.BoxSizer(wx.VERTICAL) + self.method_view = WizardMethodView(self) main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.RIGHT, -16) # Sufficient to hide the scroll bar self.SetSizer(main_sizer) @@ -17,24 +18,40 @@ def __init__(self, parent, series_service): table_columns = ["ID", "Descriptions", "Link", "Code", "Type"] self.cv_types = [] self.method_view.existing_method_table.set_columns(table_columns) + self.on_auto_radio(None) + self.all_methods = [] + self.current_method_in_series = current_method # Not the same as the selected method in the table + self.__populate_table() + self.select_current_method() + self.method_view.method_type_combo.AppendItems(self.cv_types) + self.on_existing_method_radio(None) + self.method_view.auto_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_auto_radio) self.method_view.existing_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_method_radio) self.method_view.create_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_method_radio) - self.__fetch_data() - self.method_view.method_type_combo.AppendItems(self.cv_types) + def select_current_method(self): + if self.current_method_in_series is None: + return + + if self.current_method_in_series not in self.all_methods: + return # the current method is not in the table + + index = self.all_methods.index(self.current_method_in_series) + self.method_view.existing_method_table.Focus(index) + self.method_view.existing_method_table.Select(index) def on_auto_radio(self, event): - self.method_view.existing_method_table.Enable(False) - self.__set_create_method_section_(False) + self.method_view.existing_method_table.Disable() + self.enable_create_method_section(False) def on_existing_method_radio(self, event): self.method_view.existing_method_table.Enable() - self.__set_create_method_section_(False) + self.enable_create_method_section(False) - def __set_create_method_section_(self, active): + def enable_create_method_section(self, active): if not isinstance(active, bool): raise Exception("active must be type bool") @@ -47,12 +64,12 @@ def __set_create_method_section_(self, active): def on_create_method_radio(self, event): self.method_view.existing_method_table.Disable() - self.__set_create_method_section_(True) + self.enable_create_method_section(True) - def __fetch_data(self): - methods = self.series_service.get_all_methods() + def __populate_table(self): + self.all_methods = self.series_service.get_all_methods() data = [] - for meth in methods: + for meth in self.all_methods: data.append([ meth.MethodID, meth.MethodDescription, meth.MethodLink, meth.MethodCode, @@ -87,14 +104,7 @@ def __auto_generate_a_method(self): def __select_existing_method(self): index = self.method_view.existing_method_table.GetFirstSelected() - desc = self.method_view.existing_method_table.GetItem(index, 1).GetText() - link = self.method_view.existing_method_table.GetItem(index, 2).GetText() - code = self.method_view.existing_method_table.GetItem(index, 3).GetText() - - method = self.series_service.get_method_by_code(method_code=code) - method.MethodLink = link - method.MethodDescription = desc - return method + return self.all_methods[index] def __create_new_method(self): code = self.method_view.method_code_text_ctrl.GetValue() diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 8f53e1f..3f2b18d 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -5,7 +5,7 @@ class WizardProcessLevelController(WizardPageSimple): - def __init__(self, parent, service_manager): + def __init__(self, parent, service_manager, current_processing_level=None): WizardPageSimple.__init__(self, parent) self.service_manager = service_manager @@ -14,13 +14,33 @@ def __init__(self, parent, service_manager): main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.RIGHT, -16) self.SetSizer(main_sizer) + self.all_processing_level = [] table_columns = ["Code", "Definition", "Explanation", "ID"] self.processing_level_view.existing_process_table.set_columns(table_columns) + self.current_processing_level = current_processing_level self.__fetch_data() + self.select_current_processing_level() self.processing_level_view.create_process_level_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) self.processing_level_view.existing_process_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) + def select_current_processing_level(self): + if self.current_processing_level is None: + return + + # For some reason this does not work so I'm using a loop to compare ID + # if self.current_processing_level not in self.all_processing_level: + # return + + index = -1 + for i in range(len(self.all_processing_level)): + if self.all_processing_level[i].ProcessingLevelID == self.current_processing_level.ProcessingLevelID: + index = i + break + + self.processing_level_view.existing_process_table.Focus(index) + self.processing_level_view.existing_process_table.Select(index) + def on_create_radio(self, event): self.processing_level_view.existing_process_table.Enable(False) self.__set_create_proces_section(True) @@ -39,10 +59,10 @@ def __set_create_proces_section(self, active): def __fetch_data(self): series_service = self.service_manager.get_series_service() - processes = series_service.get_all_processing_levels() + self.all_processing_level = series_service.get_all_processing_levels() data = [] - for proc in processes: + for proc in self.all_processing_level: data.append([ proc.ProcessingLevelCode, proc.Definition, @@ -62,10 +82,8 @@ def get_processing_level(self): return None def __select_existing_processing_level(self): - selected_row = self.processing_level_view.existing_process_table.get_selected_row() - code = selected_row[0] - proc_level = self.service_manager.get_series_service().get_processing_level_by_code(codes=code) - return proc_level + index = self.processing_level_view.existing_process_table.GetFirstSelected() + return self.all_processing_level[index] def __create_processing_level(self): code = self.processing_level_view.level_code_text_ctrl.GetValue() diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 4513c7f..eb30fd6 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -263,12 +263,15 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro = pageIntro.pageIntro(self, "Intro") # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - self.pgMethod = WizardMethodController(self, self.series_service) - # self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) - self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager) - # self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) - self.pgVariable = WizardVariableController(self, service_manager=service_manager, - current_variable=self.currSeries.VariableObj) + + method_from_series = self.currSeries.FeatureActionObj.ActionObj.MethodObj + variable_from_series = self.currSeries.VariableObj + processing_level_from_series = self.currSeries.ProcessingLevelObj + + self.pgMethod = WizardMethodController(self, self.series_service, current_method=variable_from_series) + self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager, current_processing_level=processing_level_from_series) + self.pgVariable = WizardVariableController(self, service_manager=service_manager, current_variable=variable_from_series) + self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.FeatureActionObj.SamplingFeatureObj) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 46f313e..9765515 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -332,7 +332,7 @@ def get_method_by_id(self, method_id): # def get_method_by_code(self, method_code): try: - self.read.getMethods(codes=[method_code])[0] + return self.read.getMethods(codes=[method_code])[0] except: return None diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index 612e232..464f64c 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -38,7 +38,8 @@ def __init__(self, parent): self.method_type_combo.SetSelection(0) self.organization_combo.SetSelection(0) self.SetupScrolling() - self.auto_method_radio.SetValue(True) + self.existing_method_radio.SetValue(True) + self.existing_method_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) # Add components to sizer table_sizer = wx.BoxSizer() diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py index b88dec6..1b9c002 100644 --- a/odmtools/view/WizardProcessLevelView.py +++ b/odmtools/view/WizardProcessLevelView.py @@ -27,6 +27,8 @@ def __init__(self, parent): font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) header_text.SetFont(font) self.SetupScrolling() + self.existing_process_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + # Add components to sizer table_sizer = wx.BoxSizer(wx.VERTICAL) From 1b3ce023daacce8e2693bb8c0ff2cc3a8502e104 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 3 Dec 2016 13:46:58 -0700 Subject: [PATCH 097/158] Fixed a bug of bugs with the save wizard Fixed a bug where it would not highlight the selected row. Selecting the current variable, method, and processing level. Updated the finished method --- odmtools/controller/WizardMethodController.py | 4 +- .../WizardProcessLevelController.py | 11 +- .../controller/WizardVariableController.py | 62 +++++--- odmtools/gui/wizSave.py | 133 +++++++++++------- odmtools/odmservices/series_service.py | 13 +- odmtools/view/WizardVariableView.py | 1 + 6 files changed, 140 insertions(+), 84 deletions(-) diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py index 113767e..2bf8d8a 100644 --- a/odmtools/controller/WizardMethodController.py +++ b/odmtools/controller/WizardMethodController.py @@ -40,7 +40,6 @@ def select_current_method(self): return # the current method is not in the table index = self.all_methods.index(self.current_method_in_series) - self.method_view.existing_method_table.Focus(index) self.method_view.existing_method_table.Select(index) def on_auto_radio(self, event): @@ -50,6 +49,7 @@ def on_auto_radio(self, event): def on_existing_method_radio(self, event): self.method_view.existing_method_table.Enable() self.enable_create_method_section(False) + self.method_view.existing_method_table.SetFocus() def enable_create_method_section(self, active): if not isinstance(active, bool): @@ -81,7 +81,7 @@ def __populate_table(self): self.method_view.existing_method_table.set_table_content(data=data) - def getMethod(self): + def get_method(self): if self.method_view.auto_method_radio.GetValue(): return self.__auto_generate_a_method() diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py index 3f2b18d..9b8cd21 100644 --- a/odmtools/controller/WizardProcessLevelController.py +++ b/odmtools/controller/WizardProcessLevelController.py @@ -28,18 +28,14 @@ def select_current_processing_level(self): if self.current_processing_level is None: return - # For some reason this does not work so I'm using a loop to compare ID - # if self.current_processing_level not in self.all_processing_level: - # return - index = -1 for i in range(len(self.all_processing_level)): if self.all_processing_level[i].ProcessingLevelID == self.current_processing_level.ProcessingLevelID: index = i break - self.processing_level_view.existing_process_table.Focus(index) - self.processing_level_view.existing_process_table.Select(index) + if index >= 0: + self.processing_level_view.existing_process_table.Select(index) def on_create_radio(self, event): self.processing_level_view.existing_process_table.Enable(False) @@ -48,6 +44,7 @@ def on_create_radio(self, event): def on_existing_radio(self, event): self.processing_level_view.existing_process_table.Enable(True) self.__set_create_proces_section(False) + self.processing_level_view.existing_process_table.SetFocus() def __set_create_proces_section(self, active): if not isinstance(active, bool): @@ -74,7 +71,7 @@ def __fetch_data(self): def get_processing_level(self): if self.processing_level_view.create_process_level_radio.GetValue(): - return self.__select_existing_processing_level() + return self.__create_processing_level() if self.processing_level_view.existing_process_radio.GetValue(): return self.__select_existing_processing_level() diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py index f20674a..709c26c 100644 --- a/odmtools/controller/WizardVariableController.py +++ b/odmtools/controller/WizardVariableController.py @@ -10,6 +10,8 @@ def __init__(self, parent, service_manager, current_variable): self.service_manager = service_manager self.current_variable = current_variable + self.all_variables = [] + main_sizer = wx.BoxSizer(wx.VERTICAL) self.variable_view = WizardVariableView(self) main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.RIGHT, -16) @@ -20,23 +22,48 @@ def __init__(self, parent, service_manager, current_variable): self.on_current_radio(None) self.__fetch_data() + self.select_current_variable() + self.variable_view.current_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_current_radio) self.variable_view.existing_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) self.variable_view.create_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) + def select_current_variable(self): + if self.current_variable is None: + return + + index = -1 + for i in range(len(self.all_variables)): + if self.all_variables[i].VariableID == self.current_variable.VariableID: + index = i + break + + if index >= 0: + self.variable_view.variable_table.Select(index) + def on_current_radio(self, event): self.variable_view.variable_table.Enable(False) - self.__set_create_variable_section(False) + self.__enable_create_variable_section(False) + + if event: + event.Skip() def on_create_radio(self, event): self.variable_view.variable_table.Enable(False) - self.__set_create_variable_section(True) + self.__enable_create_variable_section(True) + + if event: + event.Skip() def on_existing_radio(self, event): self.variable_view.variable_table.Enable(True) - self.__set_create_variable_section(False) + self.__enable_create_variable_section(False) + self.variable_view.variable_table.SetFocus() + + if event: + event.Skip() - def __set_create_variable_section(self, active): + def __enable_create_variable_section(self, active): if not isinstance(active, bool): raise Exception("active must be type bool") @@ -61,9 +88,9 @@ def __fetch_data(self): def __populate_variable_table(self): series_serivce = self.service_manager.get_series_service() - variables = series_serivce.get_all_variables() + self.all_variables = series_serivce.get_all_variables() data = [] - for var in variables: + for var in self.all_variables: data.append([var.VariableCode, var.VariableNameCV, var.SpeciationCV, @@ -74,27 +101,28 @@ def __populate_variable_table(self): self.variable_view.variable_table.set_table_content(data=data) def get_variable(self): - v = Variable() if self.variable_view.current_variable_radio.GetValue(): - v = self.current_variable - elif self.variable_view.existing_variable_radio.GetValue(): - row = self.variable_view.variable_table.get_selected_row() - code = row[0] - v = self.service_manager.get_series_service().get_variable_by_code(code) + return self.current_variable - elif self.variable_view.create_variable_radio.GetValue(): - return self.get_new_variable() + if self.variable_view.existing_variable_radio.GetValue(): + return self.__select_existing_variable() - return v + if self.variable_view.create_variable_radio.GetValue(): + return self.__create_new_variable() + + return None + + def __select_existing_variable(self): + index = self.variable_view.variable_table.GetFirstSelected() + return self.all_variables[index] - def get_new_variable(self): + def __create_new_variable(self): v = Variable() v.code = self.variable_view.variable_code_text_ctrl.GetValue() if self.variable_view.variable_code_text_ctrl.GetValue() <> "" else None v.name = self.variable_view.variable_name_combo.GetValue() if self.variable_view.variable_name_combo.GetValue() <> "" else None v.speciation = self.variable_view.speciation_combo.GetValue() if self.variable_view.speciation_combo.GetValue() <> "" else None v.variable_unit = self.service_manager.get_series_service() v.no_data_value = self.variable_view.no_data_value_text_ctrl.GetValue() if self.variable_view.no_data_value_text_ctrl.GetValue() <> "" else None - # Need unit name, time support but neither of them are in the form... return v diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index eb30fd6..a4299ba 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -225,29 +225,44 @@ def _init_ctrls(self, prnt): self.Bind(wx.wizard.EVT_WIZARD_FINISHED, self.on_wizard_finished) def get_metadata(self): - method = None - variable = None - processing_level = None - action = None + # method = self.currSeries.FeatureActionObj.ActionObj.MethodObj + # processing_level = self.currSeries.quality_control_level + # variable = self.currSeries.variable + # action = + + # if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): + # logger.debug("SaveAs") + # method = self.pgMethod.get_method() + # processing_level = self.pgQCL.get_processing_level() + # variable = self.pgVariable.get_variable() + # action = self.action_page.get_action() + # elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): + # logger.debug("Save") + # elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): + # logger.debug("Existing") + # method, processing_level, variable = self.pgExisting.getSeries() + # site = self.currSeries.FeatureActionObj.SamplingFeatureObj + # # source = self.currSeries.source + # logger.debug("site: %s, variable: %s, method: %s, source: %s, processing_level: %s" % ( + # str(site), str(variable), str(method), str(action), str(processing_level))) + # return site, variable, method, action, processing_level + # + method = self.__method_from_series + processing_level = self.__processing_level_from_series + variable = self.__variable_from_series + action = self.action_page.get_action() + site = self.__site_from_series if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): - logger.debug("SaveAs") - method = self.pgMethod.getMethod() + # Selected a new series + method = self.pgMethod.get_method() processing_level = self.pgQCL.get_processing_level() variable = self.pgVariable.get_variable() - action = self.action_page.get_action() - elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): - logger.debug("Save") - method = self.currSeries.FeatureActionObj.ActionObj.MethodObj - processing_level = self.currSeries.quality_control_level - variable = self.currSeries.variable + elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): - logger.debug("Existing") + # selected an existing series method, processing_level, variable = self.pgExisting.getSeries() - site = self.currSeries.FeatureActionObj.SamplingFeatureObj - # source = self.currSeries.source - logger.debug("site: %s, variable: %s, method: %s, source: %s, processing_level: %s" % ( - str(site), str(variable), str(method), str(action), str(processing_level))) + return site, variable, method, action, processing_level def __init__(self, parent, service_manager, record_service): @@ -262,22 +277,20 @@ def __init__(self, parent, service_manager, record_service): self.currSeries = record_service.get_series() self.pgIntro = pageIntro.pageIntro(self, "Intro") - # self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - method_from_series = self.currSeries.FeatureActionObj.ActionObj.MethodObj - variable_from_series = self.currSeries.VariableObj - processing_level_from_series = self.currSeries.ProcessingLevelObj + self.__method_from_series = self.currSeries.FeatureActionObj.ActionObj.MethodObj + self.__variable_from_series = self.currSeries.VariableObj + self.__processing_level_from_series = self.currSeries.ProcessingLevelObj + self.__all_affiliations = self.series_service.get_all_affiliations() + self.__site_from_series = self.currSeries.FeatureActionObj.SamplingFeatureObj - self.pgMethod = WizardMethodController(self, self.series_service, current_method=variable_from_series) - self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager, current_processing_level=processing_level_from_series) - self.pgVariable = WizardVariableController(self, service_manager=service_manager, current_variable=variable_from_series) + self.pgMethod = WizardMethodController(self, self.series_service, current_method=self.__method_from_series) + self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager, current_processing_level=self.__processing_level_from_series) + self.pgVariable = WizardVariableController(self, service_manager=service_manager, current_variable=self.__variable_from_series) + self.action_page = WizardActionController(self, affiliations=self.__all_affiliations) - self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, - self.currSeries.FeatureActionObj.SamplingFeatureObj) + self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.__site_from_series) - affiliations = self.series_service.get_all_affiliations() - - self.action_page = WizardActionController(self, affiliations=affiliations) self.pgSummary = SummaryPage(self, "Summary", self.series_service) @@ -331,7 +344,7 @@ def on_wizard_finishedtest(self, event): self.Close() def on_wizard_finished(self, event): - Site, Variable, Method, Source, QCL = self.get_metadata() + site, variable, method, action, proc_level = self.get_metadata() #if qcl exits use its its closeSuccessful = False @@ -345,7 +358,7 @@ def on_wizard_finished(self, event): original = self.pgExisting.pnlExisting.rbOriginal.GetValue() new = self.pgExisting.pnlExisting.rbNew.GetValue() - if QCL.id == 0 and not rbSaveAsNew: + if proc_level.ProcessingLevelID == 0 and not rbSaveAsNew: """ If we're looking at a QCL with Control level 0 and the following cases: Save @@ -380,28 +393,48 @@ def on_wizard_finished(self, event): if closeSuccessful: #if qcl exists use its id - if self.series_service.qcl_exists(QCL): - if QCL == self.currSeries.quality_control_level: - QCL = None - else: - QCL = self.record_service.get_qcl(QCL) + # if self.series_service.qcl_exists(QCL): + # if QCL == self.currSeries.quality_control_level: + # QCL = None + # else: + # QCL = self.record_service.get_qcl(QCL) + # else: + # QCL = self.record_service.create_processing_level(QCL.code, QCL.definition, QCL.explanation) + if self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) is None: + proc_level = self.series_service.create_processing_level(proc_level.ProcessingLevelCode, proc_level.Definition, proc_level.Explanation) + elif proc_level.ProcessingLevelCode == self.__processing_level_from_series.ProcessingLevelCode: + proc_level = None else: - QCL = self.record_service.create_processing_level(QCL.code, QCL.definition, QCL.explanation) + proc_level = self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) + #if variable exists use its id - if self.series_service.variable_exists(Variable): - Variable = self.record_service.get_variable(Variable) + # if self.series_service.variable_exists(Variable): + # Variable = self.record_service.get_variable(Variable) + # else: + # Variable = self.record_service.create_variable(Variable) + if self.series_service.get_variable_by_code(variable.VariableCode) is None: + variable = self.series_service.create_variable_by_var(variable) else: - Variable = self.record_service.create_variable(Variable) + variable = self.series_service.get_variable_by_code(variable.VariableCode) + #if method exists use its id - if self.series_service.method_exists(Method): - if Method == self.currSeries.method: - Method = None - else: - Method = self.record_service.get_method(Method) + # if self.series_service.method_exists(Method): + # if Method == self.currSeries.method: + # Method = None + # else: + # Method = self.record_service.get_method(Method) + # else: + # Method = self.record_service.create_method(Method) + if self.series_service.get_method_by_code(method.MethodCode) is None: + method = self.series_service.create_method(method.MethodDescription, method.MethodLink) + elif method == self.__method_from_series: + method = None else: - Method = self.record_service.create_method(Method) + method = self.series_service.get_method_by_code(method.MethodCode) + + # initiate either "Save as" or "Save" ''' @@ -415,17 +448,17 @@ def on_wizard_finished(self, event): if rbSave: result = self.record_service.save() elif rbSaveAsNew: - result = self.record_service.save_as(Variable, Method, QCL) + result = self.record_service.save_as(variable, method, proc_level) elif rbSaveAsExisting: if overwrite: - result = self.record_service.save_existing(Variable, Method, QCL) + result = self.record_service.save_existing(variable, method, proc_level) elif append: #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): #TODO if i require that original or new is selected I can call once with overwrite = original if original: - result = self.record_service.save_appending(Variable, Method, QCL, overwrite = False) + result = self.record_service.save_appending(variable, method, proc_level, overwrite = False) elif new: - result = self.record_service.save_appending(Variable, Method, QCL, overwrite = True) + result = self.record_service.save_appending(variable, method, proc_level, overwrite = True) Publisher.sendMessage("refreshSeries") diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 9765515..14f137f 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -643,8 +643,12 @@ def create_method(self, description, link): # self._edit_session.add(meth) # self._edit_session.commit() # return meth -# # +# def create_variable_by_var(self, var): + """ + :param var: Variable Object + :return: + """ try: return self.create.createVariable(var) except: @@ -934,13 +938,6 @@ def create_method(self, description, link): return self.create_service.createMethod(method=method) - def create_variable_by_var(self, var): - """ - :param var: Variable Object - :return: - """ - return self.create_service.createVariable(var=var) - def create_variable( self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py index df9a8bf..5bc106e 100644 --- a/odmtools/view/WizardVariableView.py +++ b/odmtools/view/WizardVariableView.py @@ -39,6 +39,7 @@ def __init__(self, parent): self.variable_type_combo.SetSelection(0) self.SetupScrolling() self.current_variable_radio.SetValue(True) + self.variable_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) # Add components to sizer table_sizer = wx.BoxSizer() From ba4b3417468e8ad1d3741c8bdabb9a9abbcc0495 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Sat, 3 Dec 2016 16:43:02 -0700 Subject: [PATCH 098/158] Creating action --- odmtools/controller/WizardActionController.py | 3 ++- odmtools/gui/wizSave.py | 10 +++++++++- odmtools/view/WizardActionView.py | 12 ++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/odmtools/controller/WizardActionController.py b/odmtools/controller/WizardActionController.py index 37678c2..44b2d0b 100644 --- a/odmtools/controller/WizardActionController.py +++ b/odmtools/controller/WizardActionController.py @@ -4,6 +4,7 @@ from odm2api.ODM2.models import Affiliations +# Should we rename this to Affiliations instead of actions? Action is created else where class WizardActionController(WizardPageSimple): def __init__(self, parent, affiliations): WizardPageSimple.__init__(self, parent) @@ -40,7 +41,7 @@ def populate_affiliations_table(self): self.action_view.affiliations_table.set_columns(columns) self.action_view.affiliations_table.set_table_content(data) - def get_action(self): + def get_affiliation(self): index = self.action_view.affiliations_table.GetFirstSelected() return self.affiliations[index] diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index a4299ba..4c6afda 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -9,6 +9,7 @@ import pageQCL import pageVariable import pageSummary +from odm2api.ODM2.models import Actions [wxID_PNLINTRO, wxID_PNLVARIABLE, wxID_PNLMETHOD, wxID_PNLQCL, wxID_PNLSUMMARY, wxID_WIZSAVE, wxID_PNLEXISTING, @@ -250,9 +251,10 @@ def get_metadata(self): method = self.__method_from_series processing_level = self.__processing_level_from_series variable = self.__variable_from_series - action = self.action_page.get_action() + affiliation = self.action_page.get_affiliation() site = self.__site_from_series + if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): # Selected a new series method = self.pgMethod.get_method() @@ -263,6 +265,12 @@ def get_metadata(self): # selected an existing series method, processing_level, variable = self.pgExisting.getSeries() + # Create action + action = Actions() + action.MethodObj = method + action.ActionDescription = self.action_page.action_view.description_text_box.GetValue() + action.ActionFileLink = self.action_page.action_view.action_file_link_text_box.GetValue() + return site, variable, method, action, processing_level def __init__(self, parent, service_manager, record_service): diff --git a/odmtools/view/WizardActionView.py b/odmtools/view/WizardActionView.py index 154ccaf..6a0be7c 100644 --- a/odmtools/view/WizardActionView.py +++ b/odmtools/view/WizardActionView.py @@ -34,17 +34,17 @@ def __init__(self, parent): optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) action_file_link_text = wx.StaticText(self, label="Action File Link") - action_file_link_text_box = wx.TextCtrl(self) + self.action_file_link_text_box = wx.TextCtrl(self) description_text = wx.StaticText(self, label="Description") - description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + self.description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) role_description_text = wx.StaticText(self, label="Role Description") - role_description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + self.role_description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) flex_grid_sizer = wx.FlexGridSizer(rows=3, cols=2, vgap=9, hgap=25) - flex_grid_sizer.AddMany([(action_file_link_text), (action_file_link_text_box, 1, wx.EXPAND), - (description_text), (description_text_box, 1, wx.EXPAND), - (role_description_text), (role_description_text_box, 1, wx.EXPAND) + flex_grid_sizer.AddMany([(action_file_link_text), (self.action_file_link_text_box, 1, wx.EXPAND), + (description_text), (self.description_text_box, 1, wx.EXPAND), + (role_description_text), (self.role_description_text_box, 1, wx.EXPAND) ]) flex_grid_sizer.AddGrowableRow(1, 1) From 07af4c6cc67a50c61917cf50c5f97cbedab8af7d Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 5 Dec 2016 07:49:32 -0700 Subject: [PATCH 099/158] remove cb_sort, add upsert functions --- odmtools/controller/frmSeriesSelector.py | 10 +-- odmtools/lib/ObjectListView/CellEditor.py | 4 +- odmtools/odmservices/series_service.py | 102 +++++++++------------- odmtools/odmservices/to_sql_newrows.py | 65 +++++++++----- odmtools/view/WizardMethodView.py | 4 +- odmtools/view/WizardVariableView.py | 6 +- odmtools/view/clsCreateVariable.py | 14 +-- tests/test_odmservices/test_cv_service.py | 12 +-- 8 files changed, 113 insertions(+), 104 deletions(-) diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index 4a46d46..16fea64 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -462,17 +462,17 @@ def setFilter(self, site_code='', var_code='', advfilter=''): :return: """ if site_code and var_code: - self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10],text=site_code) - self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10],text=var_code) + self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns,text=site_code) + self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns,text=var_code) self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) elif site_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10], text=site_code)) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns, text=site_code)) elif var_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10], text=var_code)) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns, text=var_code)) elif advfilter: self.tblSeries.SetFilter(advfilter) else: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:10])) + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns)) self.tblSeries.RepopulateList() diff --git a/odmtools/lib/ObjectListView/CellEditor.py b/odmtools/lib/ObjectListView/CellEditor.py index aa71423..e492304 100644 --- a/odmtools/lib/ObjectListView/CellEditor.py +++ b/odmtools/lib/ObjectListView/CellEditor.py @@ -489,12 +489,12 @@ def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) cb = wx.ComboBox(olv, choices=list(options), - style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) + style=wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)#|wx.CB_SORT) AutoCompleteHelper(cb) return cb -#------------------------------------------------------------------------- +# ------------------------------------------------------------------------- class AutoCompleteHelper(object): """ diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 5c9c5d3..3807b23 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,10 +1,9 @@ import logging -from sqlalchemy import not_ -from sqlalchemy import distinct, func +from sqlalchemy import not_, bindparam, distinct, func from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -from odmtools.odmservices import to_sql_newrows +from odmtools.odmservices import to_sql_newrows as upsert import datetime from odmtools.common.logger import LoggerTool import pandas as pd @@ -30,18 +29,12 @@ def reset_session(self): self.delete.reset_session() self.create.reset_session() - - - - ##################### # -# Get functions +# Get functions # ##################### - - def get_used_sites(self): """ Return a list of all sites that are being referenced in the Series Catalog Table @@ -73,7 +66,6 @@ def get_used_variables(self): # Query DetailedResultInfo/series object is for Display purposes - def get_all_series(self): """ Returns all series as a modelObject @@ -128,23 +120,7 @@ def get_values(self, series_id=None): :param series_id: Series id :return: pandas dataframe ''' - # series= self.get_series_by_id(series_id) - # if series: - # q = self._edit_session.query(DataValue).filter_by( - # site_id=series.site_id, - # variable_id=series.variable_id, - # method_id=series.method_id, - # source_id=series.source_id, - # quality_control_level_id=series.quality_control_level_id) - # - # query=q.statement.compile(dialect=self._session_factory.engine.dialect) - # data= pd.read_sql_query(sql= query, - # con = self._session_factory.engine, - # params = query.params ) - # #return data.set_index(data['LocalDateTime']) - # return data - # else: - # return None + setSchema(self._session_factory.engine) q = self.read._session.query(TimeSeriesResultValues) if series_id: @@ -175,7 +151,6 @@ def get_series_by_site(self , site_id): - # Site methods def get_all_sites(self): """ @@ -185,7 +160,6 @@ def get_all_sites(self): return self.read.getResults(type="site") -# def get_site_by_id(self, site_id): """ return a Site object that has an id=site_id @@ -199,15 +173,14 @@ def get_site_by_id(self, site_id): return self.read.getSampling(ids = [site_id])[0] -# -# + def get_all_variables(self): """ :return: List[Variables] """ #return self._edit_session.query(Variable).all() return self.read.getVariables() -# + def get_variable_by_id(self, variable_id): """ :param variable_id: int @@ -300,10 +273,7 @@ def get_all_processing_levels(self): # Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() # return self._edit_session.query(Qualifier).join(subquery).distinct().all() # - # Processing Level methods - def get_all_processing_level_(self): - return self.read.getProcessingLevels() -# return self._edit_session.query(QualityControlLevel).all() + def get_processing_level_by_id(self, qcl_id): try: @@ -518,12 +488,12 @@ def get_all_plot_values(self): # # # + +##################### +# +# Update functions # -# ##################### -# # -# #Update functions -# # -# ##################### +##################### # def update_series(self, series): # """ # @@ -547,7 +517,7 @@ def get_all_plot_values(self): ##################### # -#Create functions +# Create functions # ##################### @@ -599,11 +569,34 @@ def get_all_plot_values(self): # logger.info("A new series was added to the database, series id: "+str(series.id)) # return True # + def update_values(self, updates): + ''' + updates : time series result values, pandas dataframe + ''' + setSchema(self.mem_service._session_factory.engine) + + stmt = (TimeSeriesResultValues.__table__.update(). + where(TimeSeriesResultValues.ValueDateTime == bindparam('id')). + values(datavalue=bindparam('value')) + ) + + self.create._session.execute(stmt, updates["datavalue"].to_dict(orient='dict')) + #self.mem_service._session.query(TSRV).filter_by + + # self.updateDF() + def insert_annotations(self, annotations): annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) def upsert_values(self, values): - newvals= to_sql_newrows(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + newvals= upsert.clean_df_db_dups(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + filter_categorical_col= "resultdatetime" ) + self.insert_values(newvals) + delvals = upsert.delete(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + filter_categorical_col= "resultdatetime" ) + self.delete_dvs(delvals["valuedatetime"]) + + upvals = upsert.update(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, filter_categorical_col= "resultdatetime" ) pass @@ -660,7 +653,7 @@ def create_method(self, description, link): def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - #create a result and an Action object of type derivation + # ToDo: create a Result, TimeSeriesResult and an Action object of type derivation """ series_service -> Result in ODM2 :param data_values: @@ -720,20 +713,12 @@ def create_variable( :param no_data_value: :return: """ - # var = Variable() + variable = Variables() variable.VariableCode = code variable.VariableNameCV = name variable.SpeciationCV = speciation - # Commented lines indicate that Variables does not have such attributes - # var.variable_unit_id = variable_unit_id - # var.sample_medium = sample_medium - # var.value_type = value_type - # var.is_regular = is_regular - # var.time_support = time_support - # var.time_unit_id = time_unit_id - # var.data_type = data_type - # var.general_category = general_category + variable.NoDataValue = no_data_value return self.create.createVariable(var=variable) @@ -784,8 +769,7 @@ def get_samples(self): return self.read.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) def get_site_type_cvs(self): - return self.read.getCVs( - type="Site Type") # OR return self.read.getCVs(type="Sampling Feature Type") + return self.read.getCVs(type="Site Type") # OR return self.read.getCVs(type="Sampling Feature Type") def get_variable_name_cvs(self): return self.read.getCVs(type="Variable Name") @@ -811,8 +795,8 @@ def get_general_category_cvs(self): def get_censor_code_cvs(self): return self.read.getCVs(type="censorcode") - def get_sample_type_cvs(self): - return self.read.getCVs(type="Sampling Feature Type") + # def get_sample_type_cvs(self): + # return self.read.getCVs(type="Sampling Feature Type") def get_units(self): return self.read.getUnits(ids=None, name=None, type=None) diff --git a/odmtools/odmservices/to_sql_newrows.py b/odmtools/odmservices/to_sql_newrows.py index 0133a43..039583c 100644 --- a/odmtools/odmservices/to_sql_newrows.py +++ b/odmtools/odmservices/to_sql_newrows.py @@ -10,26 +10,10 @@ os.path.dirname(os.path.abspath(__file__)) -def clean_df_db_dups(df, tablename, engine, dup_cols=[], - filter_continuous_col=None, filter_categorical_col=None): - """ - Remove rows from a dataframe that already exist in a database - Required: - df : dataframe to remove duplicate rows from - engine: SQLAlchemy engine object - tablename: tablename to check duplicates in - dup_cols: list or tuple of column names to check for duplicate row values - Optional: - filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter - can be either a datetime, int, or float data type - useful for restricting the database table size to check - filter_categorical_col : the name of the categorical data column for Where = value check - Creates an "IN ()" check on the unique values in this column - Returns - Unique list of values from dataframe compared to database table - """ +def get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): + args = 'SELECT %s FROM %s' % (', '.join(['"{0}"'.format(col) for col in dup_cols]), tablename) - args_contin_filter, args_cat_filter = None, None + args_contin_filter, args_cat_filter, args_eq_filter = None, None, None if filter_continuous_col is not None: if df[filter_continuous_col].dtype == 'datetime64[ns]': args_contin_filter = """ "%s" BETWEEN Convert(datetime, '%s') @@ -41,6 +25,8 @@ def clean_df_db_dups(df, tablename, engine, dup_cols=[], args_cat_filter = ' "%s" in(%s)' % (filter_categorical_col, ', '.join(["'{0}'".format(value) for value in df[filter_categorical_col].unique()])) + # if filter_equal_col is not None: + # args_eq_filter = ' "%s" = %(s)' %(filter_categorical_col, df) if args_contin_filter and args_cat_filter: args += ' Where ' + args_contin_filter + ' AND' + args_cat_filter @@ -49,8 +35,45 @@ def clean_df_db_dups(df, tablename, engine, dup_cols=[], elif args_cat_filter: args += ' Where ' + args_cat_filter + return args + +def delete(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): + query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) df.drop_duplicates(dup_cols, keep='last', inplace=True) - df = pd.merge(df, pd.read_sql(args, engine), how='left', on=dup_cols, indicator=True) + df = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) + df = df[df['_merge'] == 'right_only'] + df.drop(['_merge'], axis=1, inplace=True) + return df + +def update(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): + query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) + #df.drop_duplicates(dup_cols, keep='last', inplace=True) + df = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) + #df = df[df['_merge'] == 'right_only'] + #df.drop(['_merge'], axis=1, inplace=True) + return df + +def clean_df_db_dups(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): + """ + Remove rows from a dataframe that already exist in a database + Required: + df : dataframe to remove duplicate rows from + engine: SQLAlchemy engine object + tablename: tablename to check duplicates in + dup_cols: list or tuple of column names to check for duplicate row values + Optional: + filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter + can be either a datetime, int, or float data type + useful for restricting the database table size to check + filter_categorical_col : the name of the categorical data column for Where = value check + Creates an "IN ()" check on the unique values in this column + Returns + Unique list of values from dataframe compared to database table + """ + + query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + df = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) df = df[df['_merge'] == 'left_only'] df.drop(['_merge'], axis=1, inplace=True) return df @@ -102,6 +125,8 @@ def to_sql_newrows(df, pool_size, *args, **kargs): [t.join() for t in workers] + + def setup(engine, tablename): engine.execute("""DROP TABLE IF EXISTS "%s" """ % (tablename)) diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py index 612e232..2e3d49b 100644 --- a/odmtools/view/WizardMethodView.py +++ b/odmtools/view/WizardMethodView.py @@ -22,11 +22,11 @@ def __init__(self, parent): method_name_text = wx.StaticText(self, label="Method Name") self.method_name_text_ctrl = wx.TextCtrl(self) method_type_text = wx.StaticText(self, label="Method Type") - self.method_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) + self.method_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) organization_text = wx.StaticText(self, label="Organization") - self.organization_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) + self.organization_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) method_link_text = wx.StaticText(self, label="Method Link") self.method_link_text_ctrl = wx.TextCtrl(self) description_text = wx.StaticText(self, label="Description") diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py index df9a8bf..bd3bbbd 100644 --- a/odmtools/view/WizardVariableView.py +++ b/odmtools/view/WizardVariableView.py @@ -19,15 +19,15 @@ def __init__(self, parent): variable_code_text = wx.StaticText(self, label="Variable Code") self.variable_code_text_ctrl = wx.TextCtrl(self) variable_name_text = wx.StaticText(self, label="Variable Name") - self.variable_name_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) + self.variable_name_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) variable_type_text = wx.StaticText(self, label="Variable Type") - self.variable_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) + self.variable_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) no_data_value_text = wx.StaticText(self, label="No Data Value") self.no_data_value_text_ctrl = wx.TextCtrl(self, value="-9999") optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) speciation_text = wx.StaticText(self, label="Speciation") - self.speciation_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY | wx.CB_SORT) + self.speciation_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY)# | wx.CB_SORT) definition_text = wx.StaticText(self, label="Definition") self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) diff --git a/odmtools/view/clsCreateVariable.py b/odmtools/view/clsCreateVariable.py index 17615eb..430c83d 100644 --- a/odmtools/view/clsCreateVariable.py +++ b/odmtools/view/clsCreateVariable.py @@ -44,7 +44,7 @@ def __init__(self, parent): cbVarNameChoices = [] self.cbVarName = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbVarNameChoices, wx.CB_READONLY | wx.CB_SORT) + cbVarNameChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer1.Add(self.cbVarName, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL | wx.EXPAND, 5) self.stUnits = wx.StaticText(self, wx.ID_ANY, u"Units:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -55,7 +55,7 @@ def __init__(self, parent): cbVarUnitsChoices = [] self.cbVarUnits = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbVarUnitsChoices, wx.CB_READONLY | wx.CB_SORT) + cbVarUnitsChoices, wx.CB_READONLY )#| wx.CB_SORT) bSizer21.Add(self.cbVarUnits, 0, wx.ALL | wx.EXPAND, 5) self.stSpeciation = wx.StaticText(self, wx.ID_ANY, u"Speciation:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -64,7 +64,7 @@ def __init__(self, parent): cbSpeciationChoices = [] self.cbSpeciation = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbSpeciationChoices, wx.CB_READONLY | wx.CB_SORT) + cbSpeciationChoices, wx.CB_READONLY)# | wx.CB_SORT) bSizer21.Add(self.cbSpeciation, 0, wx.ALL | wx.EXPAND, 5) fgSizer1.Add(bSizer21, 1, wx.EXPAND, 5) @@ -90,7 +90,7 @@ def __init__(self, parent): cbTSUnitsChoices = [] self.cbTSUnits = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbTSUnitsChoices, wx.CB_READONLY | wx.CB_SORT) + cbTSUnitsChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer3.Add(self.cbTSUnits, 0, wx.ALL | wx.EXPAND, 5) sbSizer3.Add(fgSizer3, 1, wx.ALL | wx.EXPAND, 5) @@ -107,7 +107,7 @@ def __init__(self, parent): cbValueTypeChoices = [] self.cbValueType = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbValueTypeChoices, wx.CB_READONLY | wx.CB_SORT) + cbValueTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbValueType, 0, wx.ALL | wx.EXPAND, 5) self.stDataType = wx.StaticText(self, wx.ID_ANY, u"Data Type:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -116,7 +116,7 @@ def __init__(self, parent): cbDataTypeChoices = [] self.cbDataType = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbDataTypeChoices, wx.CB_READONLY | wx.CB_SORT) + cbDataTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbDataType, 0, wx.ALL | wx.EXPAND, 5) self.stGenCat = wx.StaticText(self, wx.ID_ANY, u"General Category:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -143,7 +143,7 @@ def __init__(self, parent): cbSampleMediumChoices = [] self.cbSampleMedium = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbSampleMediumChoices, wx.CB_READONLY | wx.CB_SORT) + cbSampleMediumChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbSampleMedium, 0, wx.ALL | wx.EXPAND, 5) self.stReg = wx.StaticText(self, wx.ID_ANY, u"Is Regular:", wx.DefaultPosition, wx.DefaultSize, 0) diff --git a/tests/test_odmservices/test_cv_service.py b/tests/test_odmservices/test_cv_service.py index e3a6ccd..4231653 100644 --- a/tests/test_odmservices/test_cv_service.py +++ b/tests/test_odmservices/test_cv_service.py @@ -98,12 +98,12 @@ def test_get_censor_code_cvs(self): db_censor_code = self.cv_service.get_censor_code_cvs()[0] assert censor_code.term == db_censor_code.term - def test_get_sample_type_cvs(self): - assert self.cv_service.get_sample_type_cvs() == [] - - sample_type = test_util.add_sample_type_cv(self.session) - db_sample_type = self.cv_service.get_sample_type_cvs()[0] - assert sample_type.term == db_sample_type.term + # def test_get_sample_type_cvs(self): + # assert self.cv_service.get_sample_type_cvs() == [] + # + # sample_type = test_util.add_sample_type_cv(self.session) + # db_sample_type = self.cv_service.get_sample_type_cvs()[0] + # assert sample_type.term == db_sample_type.term def test_get_units(self): assert self.cv_service.get_units() == [] From 006dd73c9a56106c030619f48821dab1a06a73e5 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 5 Dec 2016 08:06:20 -0700 Subject: [PATCH 100/158] add stubbed code for saving --- odmtools/odmdata/memory_database.py | 26 --------------- odmtools/odmservices/edit_service.py | 2 +- odmtools/odmservices/series_service.py | 45 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 6947184..e8f0016 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -261,29 +261,3 @@ def initEditValues(self, seriesID): logger.debug("done loading database") - def changeSeriesIDs(self, result): - """ - - :param var: - :param qcl: - :param method: - :return: - """ - - query = self.mem_service._session.query(TSRV) - # if var is not None: - # logger.debug(var) - # query.update({DataValue.variable_id: var}) - # - # if method is not None: - # logger.debug(method) - # query.update({DataValue.method_id: method}) - # # check that the code is not zero - # # if qcl is not None and qcl.code != 0: - # if qcl is not None: - # logger.debug(qcl) - # query.update({DataValue.quality_control_level_id: qcl}) - logger.debug(result) - query.update({TSRV.ResultID:result}) - - diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index ccaf422..7d6b0e7 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -493,7 +493,7 @@ def updateSeries(self, result = None, is_new_series=False, overwrite = True, app result_id = result.ResultID if result is not None else None - #self.memDB.changeSeriesIDs(var_id, method_id, qcl_id) + dvs = self.memDB.getDataValuesDF() if result_id is not None: dvs["ResultID"] = result_id diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 3807b23..c942138 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -520,6 +520,51 @@ def get_all_plot_values(self): # Create functions # ##################### + def save(self, result = None): + #update result + #upsert values + #save series + pass + def saveAppend(self, overwrite = True): + #get save result + #get value count + #set in df + #update result + #count = overlap calc + #set value count = res.vc+valuecount-count + #insert values + #save series + pass + def saveAs(self): + #create series + #set in df + #insert values + #save_new_series + pass + def saveExisting(self): + #get save result + #set in df + #save(result) + pass + +#new series + def createResult(self, var, meth, proc): + #also create an action + #copy old + #change var, meth proc, in df #intend ts, agg stat + Result = None + + return self.updateResult(Result) + + def updateResult(self, Result): + #get pd + #get result + #update count, dates, + return Result + + def overlapcalc(self): + pass + # def save_series(self, series, dvs): # """ Save to an Existing Series From 96e3c6c53423fa2fd89ad7d3e9afeecca5bb2a7e Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 5 Dec 2016 12:25:07 -0700 Subject: [PATCH 101/158] update annotations query --- odmtools/odmdata/memory_database.py | 2 ++ odmtools/odmservices/series_service.py | 39 ++++++++++++++++++-------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index e8f0016..093d1a5 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -68,12 +68,14 @@ def getDataValuesDF(self): #else: # self.updateDF() ''' + setSchema(self.mem_service._session_factory.engine) self.updateDF() # pick up thread here before it is needed logging.debug("done updating memory dataframe") return self.df def getDataValues(self): + setSchema(self.mem_service._session_factory.engine) return self.mem_service.get_all_values() def getEditRowCount(self): diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 630f66f..8405a4f 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -391,7 +391,7 @@ def get_all_values_df(self): :return: Pandas DataFrame object """ - q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) + q = self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, params=query.params) @@ -416,11 +416,11 @@ def get_all_values_list(self): :return: """ - result = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() + result =self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() return [x.list_repr() for x in result] def get_all_values(self): - return self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() + return self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() # @staticmethod def calcSeason(row): @@ -520,12 +520,15 @@ def get_all_plot_values(self): # Create functions # ##################### - def save(self, result = None): + + + def save(self, values, result = None): #update result #upsert values #save series + #save new annotations pass - def saveAppend(self, overwrite = True): + def saveAppend(self,value, overwrite = True): #get save result #get value count #set in df @@ -534,17 +537,22 @@ def saveAppend(self, overwrite = True): #set value count = res.vc+valuecount-count #insert values #save series + #save new annotations pass - def saveAs(self): + def saveAs(self, values): #create series #set in df #insert values #save_new_series + #get all annotations for series + #save all annotations + pass - def saveExisting(self): + def saveExisting(self, values): #get save result #set in df - #save(result) + #save(values, result) + pass #new series @@ -635,15 +643,15 @@ def insert_annotations(self, annotations): def upsert_values(self, values): newvals= upsert.clean_df_db_dups(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_categorical_col= "resultdatetime" ) + filter_continuous_col="valuedatetime", filter_categorical_col="resultid") self.insert_values(newvals) delvals = upsert.delete(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_categorical_col= "resultdatetime" ) + filter_continuous_col = "valuedatetime", filter_categorical_col = "resultid") self.delete_dvs(delvals["valuedatetime"]) upvals = upsert.update(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_categorical_col= "resultdatetime" ) - pass + filter_continuous_col="valuedatetime", filter_categorical_col="resultid") + self.update(upvals) def insert_values(self, values): """ @@ -825,6 +833,13 @@ def get_annotation_by_code(self, code): def get_all_annotations(self): return self.read.getAnnotations(type=None) + def get_annotations_by_result(self, resultid): + setSchema(self._session_factory.engine) + ids = [x[0] for x in self.read._session.query(TimeSeriesResultValues.ValueID)\ + .filter(TimeSeriesResultValues.ResultID == resultid).all()] + return self.read._session.query(TimeSeriesResultValueAnnotations)\ + .filter(TimeSeriesResultValueAnnotations.ValueID.in_(ids)).all() + def get_aggregation_statistic(self): return self.read.getCVs(type="aggregationstatistic") From 84bb10e48e4cb934fe9d15a3382bb8cb12ed8a44 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 5 Dec 2016 12:45:06 -0700 Subject: [PATCH 102/158] Updated the summary page on the save wizard --- odmtools/gui/pageSummary.py | 2 +- odmtools/gui/wizSave.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/odmtools/gui/pageSummary.py b/odmtools/gui/pageSummary.py index c7b3b0e..bdd8a14 100644 --- a/odmtools/gui/pageSummary.py +++ b/odmtools/gui/pageSummary.py @@ -64,7 +64,7 @@ def __init__(self, parent, id, pos, size, style): self.md=self.AppendItem(self.m, 'Description: ') - self.soc=self.AppendItem(self.action, 'Person: ') + # self.soc=self.AppendItem(self.action, 'Person: ') self.soo=self.AppendItem(self.action, 'Organization: ') self.sod=self.AppendItem(self.action, 'Description: ') diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 4c6afda..dab63bb 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -193,9 +193,8 @@ def fill_summary(self): self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.MethodDescription)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Person: ' + str(action.PersonObj.PersonFirstName + " " + action.PersonObj.PersonLastName)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(action.OrganizationObj.OrganizationName)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(action.OrganizationObj.OrganizationDescription)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(action.MethodObj.OrganizationObj.OrganizationName)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(action.MethodObj.OrganizationObj.OrganizationDescription)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.ProcessingLevelCode)) self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.Definition)) @@ -270,6 +269,7 @@ def get_metadata(self): action.MethodObj = method action.ActionDescription = self.action_page.action_view.description_text_box.GetValue() action.ActionFileLink = self.action_page.action_view.action_file_link_text_box.GetValue() + action.MethodObj.OrganizationObj = affiliation.OrganizationObj return site, variable, method, action, processing_level From 9b1c4700cd6ecb0366f3f712ad8fa0305ada9697 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 6 Dec 2016 11:15:44 -0700 Subject: [PATCH 103/158] Small changes to the add point form --- odmtools/gui/wizSave.py | 4 ++++ odmtools/odmdata/memory_database.py | 14 +++++--------- odmtools/odmservices/series_service.py | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index dab63bb..12dd377 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -452,6 +452,10 @@ def on_wizard_finished(self, event): result = self.record_service.saveAs(Variable, Method, QCL, True) ''' + # Create action + new_result = self.series_service.createResult(var=variable, meth=method, proc=proc_level) + # action = self.series_service.create.createAction(action) + try: if rbSave: result = self.record_service.save() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 093d1a5..825fd5c 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -196,20 +196,16 @@ def addPoints(self, points): points = [points] for point in points: - vals = {"DataValue": point[0], "LocalDateTime": point[1], - "DateTimeUTC": point[2], "UTCOffset": point[3], - "CensorCode": point[4], "QualityCode": point[5], - "TimeAggregationInterval": point[6], "TImeAggregationUnitID": point[7], - "Annotation": point[8], "SiteID": point[9], - "VariableID": point[10], "MethodID": point[11], - "OrganizationID": point[12], "ProcessID": point[13] + vals = {"datavalue": point[0], "valuedatetime": point[1], + "valuedatetimeutcoffset": point[3], + "censorcodecv": point[4], "qualitycodecv": point[5], + "timeaggregationinterval": point[6], "timeaggregationintervalunitsid": point[7] + # todo: Add annotations } setSchema(self.mem_service._session_factory.engine) self.mem_service._session.execute(stmt, vals) - - def stopEdit(self): self.editLoaded = False self.df = None diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 8405a4f..4f40c2e 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -561,6 +561,7 @@ def createResult(self, var, meth, proc): #copy old #change var, meth proc, in df #intend ts, agg stat Result = None + result = Results() return self.updateResult(Result) From 42c1d99be0005bc13207288cf636515906742549 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 6 Dec 2016 12:09:19 -0700 Subject: [PATCH 104/158] Created action ,action by, and result when clicking finihsed in save wizard. Fixed a mistake when calculaing utc offset. Attached ID to results and actions --- odmtools/gui/wizSave.py | 14 +++++++-- odmtools/odmservices/series_service.py | 42 +++++++++++++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 12dd377..57db0d0 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -9,7 +9,7 @@ import pageQCL import pageVariable import pageSummary -from odm2api.ODM2.models import Actions +from odm2api.ODM2.models import * [wxID_PNLINTRO, wxID_PNLVARIABLE, wxID_PNLMETHOD, wxID_PNLQCL, wxID_PNLSUMMARY, wxID_WIZSAVE, wxID_PNLEXISTING, @@ -267,9 +267,11 @@ def get_metadata(self): # Create action action = Actions() action.MethodObj = method + action.MethodID = method.MethodID action.ActionDescription = self.action_page.action_view.description_text_box.GetValue() action.ActionFileLink = self.action_page.action_view.action_file_link_text_box.GetValue() action.MethodObj.OrganizationObj = affiliation.OrganizationObj + action.BeginDateTime = self.currSeries.ResultDateTime return site, variable, method, action, processing_level @@ -442,8 +444,6 @@ def on_wizard_finished(self, event): else: method = self.series_service.get_method_by_code(method.MethodCode) - - # initiate either "Save as" or "Save" ''' if self.page1.pnlIntroduction.rbSave.GetValue(): @@ -456,6 +456,14 @@ def on_wizard_finished(self, event): new_result = self.series_service.createResult(var=variable, meth=method, proc=proc_level) # action = self.series_service.create.createAction(action) + affiliation = self.action_page.get_affiliation() + + new_action_by = ActionBy() + new_action_by.ActionID = action.ActionID + new_action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() + new_action_by.AffiliationID = affiliation.AffiliationID + new_action_by.AffiliationObj = affiliation + try: if rbSave: result = self.record_service.save() diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4f40c2e..ec5c1fe 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -560,10 +560,38 @@ def createResult(self, var, meth, proc): #also create an action #copy old #change var, meth proc, in df #intend ts, agg stat - Result = None + # Result = None + # result = Results() result = Results() - return self.updateResult(Result) + if isinstance(var, Variables): + result.VariableID = var.VariableID + result.VariableObj = var + + if isinstance(meth, Methods): + # do something with meth + pass + + if isinstance(proc, ProcessingLevels): + result.ProcessingLevelID = proc.ProcessingLevelID + result.ProcessingLevelObj = proc + + time, offset = self.get_current_time_and_utcoffset() + result.ResultDateTime = time + result.ResultDateTimeUTCOffset = offset + + self.create.createResult(result=result) + + return self.updateResult(result) + + def get_current_time_and_utcoffset(self): + current_time = datetime.datetime.now() + utc_time = datetime.datetime.utcnow() + + difference_in_timezone = current_time - utc_time + offset_in_hours = difference_in_timezone.total_seconds() / 3600 + + return current_time, offset_in_hours def updateResult(self, Result): #get pd @@ -768,13 +796,11 @@ def create_annotation(self, code, text, link=None): annotation.AnnotationCode = code annotation.AnnotationText = text annotation.AnnotationTypeCV = "Time series result value annotation" - current_time = datetime.datetime.now() - utc_time = datetime.datetime.utcnow() - annotation.AnnotationDateTime = current_time - difference_in_timezone = utc_time - current_time - offset_in_hours = difference_in_timezone.seconds / 3600 - annotation.AnnotationUTCOffset = offset_in_hours + time, offset = self.get_current_time_and_utcoffset() + annotation.AnnotationDateTime = time + annotation.AnnotationUTCOffset = offset + annotation.AnnotationLink = link return self.create_annotation_by_anno(annotation) From ed8f5dd3103caa8463024d3346a0ddb5d6feca3b Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 12:43:34 -0700 Subject: [PATCH 105/158] merge conflict --- odmtools/odmservices/edit_service.py | 66 ++++++++++++++++++++++++++ odmtools/odmservices/series_service.py | 36 +------------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 7d6b0e7..d1a092a 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -481,6 +481,72 @@ def restore(self): self._populate_series() self.reset_filter() + def save(self, values, result=None): + # update result + # upsert values + # save series + # save new annotations + pass + + def saveAppend(self, value, overwrite=True): + # get save result + # get value count + # set in df + # update result + # count = overlap calc + # set value count = res.vc+valuecount-count + # insert values + # save series + # save new annotations + pass + + def saveAs(self, values): + # create series + # set in df + # insert values + # save_new_series + # get all annotations for series + # save all annotations + + pass + + def saveExisting(self, values): + # get save result + # set in df + # save(values, result) + + pass + + # new series + + def createResult(self, var, meth, proc): + # also create an action + # copy old + # change var, meth proc, in df #intend ts, agg stat + Result = None + + return self.updateResult(Result) + + def updateResult(self, Result): + # get pd + # get result + # update count, dates, + return Result + + def overlapcalc(self): + pass + + + + + + + + + + + + def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): """ diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index ec5c1fe..eb14b1b 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -522,38 +522,6 @@ def get_all_plot_values(self): ##################### - def save(self, values, result = None): - #update result - #upsert values - #save series - #save new annotations - pass - def saveAppend(self,value, overwrite = True): - #get save result - #get value count - #set in df - #update result - #count = overlap calc - #set value count = res.vc+valuecount-count - #insert values - #save series - #save new annotations - pass - def saveAs(self, values): - #create series - #set in df - #insert values - #save_new_series - #get all annotations for series - #save all annotations - - pass - def saveExisting(self, values): - #get save result - #set in df - #save(values, result) - - pass #new series def createResult(self, var, meth, proc): @@ -599,8 +567,6 @@ def updateResult(self, Result): #update count, dates, return Result - def overlapcalc(self): - pass # def save_series(self, series, dvs): @@ -651,6 +617,7 @@ def overlapcalc(self): # logger.info("A new series was added to the database, series id: "+str(series.id)) # return True # + def update_values(self, updates): ''' updates : time series result values, pandas dataframe @@ -670,6 +637,7 @@ def update_values(self, updates): def insert_annotations(self, annotations): annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) + def upsert_values(self, values): newvals= upsert.clean_df_db_dups(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, filter_continuous_col="valuedatetime", filter_categorical_col="resultid") From 5eac1c71eb3149685e6dc572d99503d7c0e11d7f Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 15:34:00 -0700 Subject: [PATCH 106/158] begin adding saving functionality, add commit to editing db for temp fix) --- odmtools/gui/wizSave.py | 4 + odmtools/odmdata/memory_database.py | 13 +- odmtools/odmservices/edit_service.py | 371 +++++++++++++------------ odmtools/odmservices/series_service.py | 143 +++++----- 4 files changed, 285 insertions(+), 246 deletions(-) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 57db0d0..73e4735 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -452,8 +452,12 @@ def on_wizard_finished(self, event): result = self.record_service.saveAs(Variable, Method, QCL, True) ''' + + TODO: move all of this stuff into the edit_service file # Create action new_result = self.series_service.createResult(var=variable, meth=method, proc=proc_level) + #TODO create a timeseriesresult + #TODO create a featureaction # action = self.series_service.create.createAction(action) affiliation = self.action_page.get_affiliation() diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 825fd5c..fe399dc 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -29,6 +29,7 @@ def __init__(self, taskserver=None): # Memory_service handles in memory database sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + setSchema(self.mem_service._session_factory.engine) # TODO clean up closing of program @@ -68,6 +69,8 @@ def getDataValuesDF(self): #else: # self.updateDF() ''' + # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore + self.mem_service._session.commit() setSchema(self.mem_service._session_factory.engine) self.updateDF() # pick up thread here before it is needed @@ -75,6 +78,8 @@ def getDataValuesDF(self): return self.df def getDataValues(self): + # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore + self.mem_service._session.commit() setSchema(self.mem_service._session_factory.engine) return self.mem_service.get_all_values() @@ -97,6 +102,7 @@ def getEditDataValuesforGraph(self): def commit(self): self.mem_service._session.commit() + # self.mem_service._session.commit() def rollback(self): self.mem_service._session.rollback() @@ -143,6 +149,7 @@ def updateValue(self, ids, operator, value): q=self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c)) q.update({TSRV.DataValue: query}, False) + #self.updateDF() def chunking(self, data): @@ -175,6 +182,7 @@ def updateFlag(self, ids, value): frames = [self.annotation_list, flags] self.annotation_list=pd.concat(frames) print self.annotation_list + #todo: remove duplicates before saving @@ -199,9 +207,12 @@ def addPoints(self, points): vals = {"datavalue": point[0], "valuedatetime": point[1], "valuedatetimeutcoffset": point[3], "censorcodecv": point[4], "qualitycodecv": point[5], - "timeaggregationinterval": point[6], "timeaggregationintervalunitsid": point[7] + "timeaggregationinterval": point[6], "timeaggregationintervalunitsid": point[7], + "resultid":self.df["resultid"][0] # todo: Add annotations } + if point[8]: + self.updateFlag(point[1], self.series_service.get_annotation_by_code(point[8].split(':')[0]).AnnotationID) setSchema(self.mem_service._session_factory.engine) self.mem_service._session.execute(stmt, vals) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index d1a092a..5402b6c 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -481,46 +481,64 @@ def restore(self): self._populate_series() self.reset_filter() - def save(self, values, result=None): + def save(self, result=None): + if not result: + result = self.memDB.series_service.get_series(series_id = self.memDB.df['resultid'])[0] # update result + self.updateResult(result) # upsert values - # save series + self.memDB.series_service.upsert(self.memDB.df) # save new annotations - pass + self.add_annotations(self.memDB.annotation_list) + return result - def saveAppend(self, value, overwrite=True): + def saveAppend(self, variable, method, proc_level, overwrite=True): # get save result + result= self.memDB.series_service.get_series_by_id_quint(variable, method, proc_level) # get value count # set in df # update result + self.updateResult(result) # count = overlap calc # set value count = res.vc+valuecount-count # insert values # save series # save new annotations - pass + return result + - def saveAs(self, values): + def saveAs(self, variable, method, proc_level): # create series + result= self.createResult(variable, method, proc_level) + # update result + self.updateResult(result) # set in df # insert values + # save_new_series # get all annotations for series # save all annotations - pass + return result - def saveExisting(self, values): + def saveExisting(self, variable, method, proc_level): # get save result + result = self.memDB.series_service.get_series_by_id_quint(variable, method, proc_level) # set in df # save(values, result) - - pass + self.memDB.series_service. + return result # new series def createResult(self, var, meth, proc): - # also create an action + + #if result does not exist + #create Action : of type "derivation" + #create Actionby + #create FeatureAction( using current sampling feature id + #create TimeSeriesResult- this should also contain all of the stuff for the Result + # copy old # change var, meth proc, in df #intend ts, agg stat Result = None @@ -536,6 +554,8 @@ def updateResult(self, Result): def overlapcalc(self): pass + def add_annotations(self, annolist): + pass @@ -545,171 +565,170 @@ def overlapcalc(self): - - - def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): - """ - - :param var: - :param method: - :param qcl: - :param is_new_series: - :return: - """ - - result_id = result.ResultID if result is not None else None - - - dvs = self.memDB.getDataValuesDF() - if result_id is not None: - dvs["ResultID"] = result_id - - - #if is new series_service remove valueids - #if is_new_series: - dvs["ValueID"] = None - ''' - for dv in dvs: - dv.id = None - ''' - - series = self.memDB.series_service.get_series_by_id(self._series_id) - logger.debug("original editing series_service id: %s" % str(series.id)) - - if (result): - tseries = self.memDB.series_service.get_series(result_id) - - if tseries: - logger.debug("Save existing series_service ID: %s" % str(tseries.id)) - series = tseries - else: - print "Series doesn't exist (if you are not, you should be running SaveAs)" - - if is_new_series: - series = series_module.copy_series(series) - if var: - series.variable_id = var_id - series.variable_code = var.code - series.variable_name = var.name - series.speciation = var.speciation - series.variable_units_id = var.variable_unit_id - series.variable_units_name = var.variable_unit.name - series.sample_medium = var.sample_medium - series.value_type = var.value_type - series.time_support = var.time_support - series.time_units_id = var.time_unit_id - series.time_units_name = var.time_unit.name - series.data_type = var.data_type - series.general_category = var.general_category - - if method: - series.method_id = method_id - series.method_description = method.description - - if qcl: - series.quality_control_level_id = qcl_id - series.quality_control_level_code = qcl.code - ''' - dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) - dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) - ''' - - form = "%Y-%m-%d %H:%M:%S" - - if not append: - - series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[c0].local_date_time - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time - series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc - series.value_count = len(dvs) - - ## Override previous save - if not is_new_series: - # delete old dvs - #pass - self.memDB.series_service.delete_values_by_series(series) - elif append: - #if series_service end date is after dvs startdate - dbend = series.end_date_time - dfstart = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form) - overlap = dbend>= dfstart - #leave series_service start dates to those previously set - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form) - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) - #TODO figure out how to calculate the new value count - series.value_count = len(dvs) - - if overlap: - if overwrite: - #remove values from the database - self.memDB.series_service.delete_values_by_series(series, startdate=dfstart) - else: - #remove values from df - dvs = dvs[dvs["LocalDateTime"] > dbend] - - - - #logger.debug("series_service.data_values: %s" % ([x for x in series_service.data_values])) - dvs.drop('ValueID', axis=1, inplace=True) - return series, dvs - - def save(self): - """ Save to an existing catalog - :param var: - :param method: - :param qcl: - :return: - """ - - series, dvs = self.updateSeries(is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series_service saved!") - return True - else: - logger.debug("The Save was unsuccessful") - return False - - def save_as(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) - - if self.memDB.series_service.save_new_series(series, dvs): - logger.debug("series_service saved!") - return True - else: - logger.debug("The Save As Function was Unsuccessful") - return False - - def save_appending(self, var= None, method = None, qcl=None, overwrite=False): - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False, append= True, overwrite=overwrite) - - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series_service saved!") - return True - else: - logger.debug("The Append Existing Function was Unsuccessful") - return False - - def save_existing(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series_service saved!") - return True - else: - logger.debug("The Save As Existing Function was Unsuccessful") - return False + # + # def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): + # """ + # + # :param var: + # :param method: + # :param qcl: + # :param is_new_series: + # :return: + # """ + # + # result_id = result.ResultID if result is not None else None + # + # + # dvs = self.memDB.getDataValuesDF() + # if result_id is not None: + # dvs["ResultID"] = result_id + # + # + # #if is new series_service remove valueids + # #if is_new_series: + # dvs["ValueID"] = None + # ''' + # for dv in dvs: + # dv.id = None + # ''' + # + # series = self.memDB.series_service.get_series_by_id(self._series_id) + # logger.debug("original editing series_service id: %s" % str(series.id)) + # + # if (result): + # tseries = self.memDB.series_service.get_series(result_id) + # + # if tseries: + # logger.debug("Save existing series_service ID: %s" % str(tseries.id)) + # series = tseries + # else: + # print "Series doesn't exist (if you are not, you should be running SaveAs)" + # + # if is_new_series: + # series = series_module.copy_series(series) + # if var: + # series.variable_id = var_id + # series.variable_code = var.code + # series.variable_name = var.name + # series.speciation = var.speciation + # series.variable_units_id = var.variable_unit_id + # series.variable_units_name = var.variable_unit.name + # series.sample_medium = var.sample_medium + # series.value_type = var.value_type + # series.time_support = var.time_support + # series.time_units_id = var.time_unit_id + # series.time_units_name = var.time_unit.name + # series.data_type = var.data_type + # series.general_category = var.general_category + # + # if method: + # series.method_id = method_id + # series.method_description = method.description + # + # if qcl: + # series.quality_control_level_id = qcl_id + # series.quality_control_level_code = qcl.code + # ''' + # dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) + # dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) + # ''' + # + # form = "%Y-%m-%d %H:%M:%S" + # + # if not append: + # + # series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[c0].local_date_time + # series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time + # series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc + # series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc + # series.value_count = len(dvs) + # + # ## Override previous save + # if not is_new_series: + # # delete old dvs + # #pass + # self.memDB.series_service.delete_values_by_series(series) + # elif append: + # #if series_service end date is after dvs startdate + # dbend = series.end_date_time + # dfstart = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form) + # overlap = dbend>= dfstart + # #leave series_service start dates to those previously set + # series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form) + # series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) + # #TODO figure out how to calculate the new value count + # series.value_count = len(dvs) + # + # if overlap: + # if overwrite: + # #remove values from the database + # self.memDB.series_service.delete_values_by_series(series, startdate=dfstart) + # else: + # #remove values from df + # dvs = dvs[dvs["LocalDateTime"] > dbend] + # + # + # + # #logger.debug("series_service.data_values: %s" % ([x for x in series_service.data_values])) + # dvs.drop('ValueID', axis=1, inplace=True) + # return series, dvs + # + # def save(self): + # """ Save to an existing catalog + # :param var: + # :param method: + # :param qcl: + # :return: + # """ + # + # series, dvs = self.updateSeries(is_new_series=False) + # if self.memDB.series_service.save_series(series, dvs): + # logger.debug("series_service saved!") + # return True + # else: + # logger.debug("The Save was unsuccessful") + # return False + # + # def save_as(self, var=None, method=None, qcl=None): + # """ + # :param var: + # :param method: + # :param qcl: + # :return: + # """ + # series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) + # + # if self.memDB.series_service.save_new_series(series, dvs): + # logger.debug("series_service saved!") + # return True + # else: + # logger.debug("The Save As Function was Unsuccessful") + # return False + # + # def save_appending(self, var= None, method = None, qcl=None, overwrite=False): + # series, dvs = self.updateSeries(var, method, qcl, is_new_series=False, append= True, overwrite=overwrite) + # + # if self.memDB.series_service.save_series(series, dvs): + # logger.debug("series_service saved!") + # return True + # else: + # logger.debug("The Append Existing Function was Unsuccessful") + # return False + # + # def save_existing(self, var=None, method=None, qcl=None): + # """ + # :param var: + # :param method: + # :param qcl: + # :return: + # """ + # series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) + # if self.memDB.series_service.save_series(series, dvs): + # logger.debug("series_service saved!") + # return True + # else: + # logger.debug("The Save As Existing Function was Unsuccessful") + # return False def create_qcl(self, code, definition, explanation): return self.memDB.series_service.create_processing_level(code, definition, explanation) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index eb14b1b..63ee9a1 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,9 +1,9 @@ import logging -from sqlalchemy import not_, bindparam, distinct, func +from sqlalchemy import not_, bindparam, distinct, func, exists from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -from odmtools.odmservices import to_sql_newrows as upsert +from odmtools.odmservices.to_sql_newrows import clean_df_db_dups, delete, update import datetime from odmtools.common.logger import LoggerTool import pandas as pd @@ -316,39 +316,46 @@ def get_method_by_code(self, method_code): # #todo: Take another look at this - # def get_samples_by_series_id(self, series_id): - # # """ - # # - # # :param series_id: - # # :return: - # # # """ - # # subquery = self._edit_session.query(DataValue.sample_id).outerjoin( - # # Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() - # # return self._edit_session.query(Sample).join(subquery).distinct().all() - -# -# # Series Catalog methods -# def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): -# """ -# -# :param site_id: -# :param var_id: -# :param method_id: -# :param source_id: -# :param qcl_id: -# :return: Series -# """ -# try: -# return self._edit_session.query(Series).filter_by( -# site_id=site_id, variable_id=var_id, method_id=method_id, -# source_id=source_id, quality_control_level_id=qcl_id).first() -# except: -# return None -# -# def get_series_from_filter(self): -# # Pass in probably a Series object, match it against the database -# pass -# + + + # Series Catalog methods + def get_series_by_id_quint(self, result): + """ + + :param site_id: + :param var_id: + :param method_id: + :param source_id: + :param qcl_id: + :return: Series + """ + #unique Result + #FeatureActionID, ResultTypeCV, VariableID, UnitsID, ProcessingLevelID, SampledMediumCV + + + try: + # return self._edit_session.query(Results).filter_by( + # VariableID=var_id, MethodID=method_id, + # AnnotationID=qcl_id).first() + (ret, ), = self._session.query(exists(). + where(Results.FeatureActionID == result.FeatureActionID). + where(Results.ResultTypeCV == result.ResultTypeCV). + where(Results.VariableID == result.VariableID). + where(Results.UnitsID == result.UnitsID). + where(Results.ProcessingLevelID == result.ProcessingLevelID). + wehre(Results.SampledMediumCV == result.SampledMediumCV) + + + ) + + return ret + except: + return None + + def get_series_from_filter(self): + # Pass in probably a Series object, match it against the database + pass + # #Data Value Methods def get_values(self, series_id=None): @@ -548,9 +555,9 @@ def createResult(self, var, meth, proc): result.ResultDateTime = time result.ResultDateTimeUTCOffset = offset - self.create.createResult(result=result) + return self.create.createResult(result=result) + - return self.updateResult(result) def get_current_time_and_utcoffset(self): current_time = datetime.datetime.now() @@ -561,11 +568,7 @@ def get_current_time_and_utcoffset(self): return current_time, offset_in_hours - def updateResult(self, Result): - #get pd - #get result - #update count, dates, - return Result + @@ -593,30 +596,30 @@ def updateResult(self, Result): # raise Exception("Series does not exist, unable to save. Please select 'Save As'") # # -# def save_new_series(self, series, dvs): -# """ Create as a new catalog entry -# :param series: -# :param data_values: -# :return: -# """ -# # Save As case -# if self.series_exists(series): -# msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" -# logger.info(msg) -# raise Exception(msg) -# else: -# try: -# self._edit_session.add(series) -# self._edit_session.commit() -# self.save_values(dvs) -# #self._edit_session.add_all(dvs) -# except Exception as e: -# self._edit_session.rollback() -# raise e -# -# logger.info("A new series was added to the database, series id: "+str(series.id)) -# return True -# + def save_new_series(self, series, dvs): + """ Create as a new catalog entry + :param series: + :param data_values: + :return: + """ + # Save As case + if self.series_exists(series): + msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" + logger.info(msg) + raise Exception(msg) + else: + try: + self._edit_session.add(series) + self._edit_session.commit() + self.save_values(dvs) + #self._edit_session.add_all(dvs) + except Exception as e: + self._edit_session.rollback() + raise e + + logger.info("A new series was added to the database, series id: "+str(series.id)) + return True + def update_values(self, updates): ''' @@ -639,14 +642,15 @@ def insert_annotations(self, annotations): def upsert_values(self, values): - newvals= upsert.clean_df_db_dups(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + setSchema(self._session_factory.engine) + newvals= clean_df_db_dups(df = values, dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, filter_continuous_col="valuedatetime", filter_categorical_col="resultid") self.insert_values(newvals) - delvals = upsert.delete(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + delvals = delete(df = values, dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, filter_continuous_col = "valuedatetime", filter_categorical_col = "resultid") self.delete_dvs(delvals["valuedatetime"]) - upvals = upsert.update(df = values, tablename="timeseriesresultvalues", engine = self._session_factory.engine, + upvals = update(df = values,dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, filter_continuous_col="valuedatetime", filter_categorical_col="resultid") self.update(upvals) @@ -905,6 +909,7 @@ def delete_dvs(self, id_list): :param id_list: list of datetimes :return: """ + setSchema(self._session_factory.engine) try: self.delete.deleteTSRValues(dates=id_list) except Exception as ex: From 11f5f81091eacd5a6918703e9f8b631081a31c8d Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 17:16:25 -0700 Subject: [PATCH 107/158] alter wizsave --- odmtools/controller/pageExisting.py | 1 + odmtools/gui/wizSave.py | 14 ++- odmtools/odmservices/edit_service.py | 80 +++++++++---- odmtools/odmservices/series_service.py | 111 +++++++++++------- tests/test_odmservices/test_series_service.py | 6 +- 5 files changed, 140 insertions(+), 72 deletions(-) diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 7d5e780..841fc0e 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -80,4 +80,5 @@ def initTable(self, dbservice, site_id): self.pnlExisting.olvSeriesList.SetColumns(seriesColumns) objects = dbservice.get_series_by_site(site_id=site_id) + self.pnlExisting.olvSeriesList.SetObjects(objects) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 73e4735..fab74a8 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -226,7 +226,7 @@ def _init_ctrls(self, prnt): def get_metadata(self): # method = self.currSeries.FeatureActionObj.ActionObj.MethodObj - # processing_level = self.currSeries.quality_control_level + # processing_level = self.currSeriefs.quality_control_level # variable = self.currSeries.variable # action = @@ -453,11 +453,10 @@ def on_wizard_finished(self, event): ''' - TODO: move all of this stuff into the edit_service file + #TODO: move all of this stuff into the edit_service file # Create action - new_result = self.series_service.createResult(var=variable, meth=method, proc=proc_level) - #TODO create a timeseriesresult - #TODO create a featureaction + new_result = self.series_service.getResult(var=variable, meth=method, proc=proc_level) + # action = self.series_service.create.createAction(action) affiliation = self.action_page.get_affiliation() @@ -468,15 +467,20 @@ def on_wizard_finished(self, event): new_action_by.AffiliationID = affiliation.AffiliationID new_action_by.AffiliationObj = affiliation + #TODO end + try: if rbSave: result = self.record_service.save() elif rbSaveAsNew: + #TODO send in Action, and Actionby result = self.record_service.save_as(variable, method, proc_level) elif rbSaveAsExisting: if overwrite: + #TODO send in just the result result = self.record_service.save_existing(variable, method, proc_level) elif append: + #TODO send in just the result #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): #TODO if i require that original or new is selected I can call once with overwrite = original if original: diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 5402b6c..c14ccdc 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -482,71 +482,103 @@ def restore(self): self.reset_filter() def save(self, result=None): + values = self.memDB.df if not result: - result = self.memDB.series_service.get_series(series_id = self.memDB.df['resultid'])[0] + result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) # update result self.updateResult(result) # upsert values - self.memDB.series_service.upsert(self.memDB.df) + self.memDB.series_service.upsert(values) # save new annotations self.add_annotations(self.memDB.annotation_list) return result - def saveAppend(self, variable, method, proc_level, overwrite=True): + def saveAppend(self, result, overwrite=True): + values = self.memDB.df + # get save result - result= self.memDB.series_service.get_series_by_id_quint(variable, method, proc_level) + # get value count + vc = result.ValueCount # set in df + values["resultid"] = result.ResultID # update result self.updateResult(result) # count = overlap calc + count = self.overlapcalc() # set value count = res.vc+valuecount-count # insert values - # save series + self.memDB.series_service.upsert_values(values) # save new annotations + self.add_annotations(self.memDB.annotation_list) return result - def saveAs(self, variable, method, proc_level): + def saveAs(self, variable, method, proc_level, action, action_by): + values = self.memDB.df + # get all annotations for series + annolist= self.memDB.series_service.get_annotations_by_result(values["resultid"][0]) + annolist['valueid'] + # create series - result= self.createResult(variable, method, proc_level) + result= self.getResult(variable, method, proc_level) # update result self.updateResult(result) # set in df + values["resultid"] = result.ResultID # insert values + self.memDB.series_service.insert_values(values) - # save_new_series - # get all annotations for series # save all annotations + frames = [self.memDB.annotation_list, annolist] + annolist = pd.concat(frames) + self.add_annotations(annolist) + return result - def saveExisting(self, variable, method, proc_level): + def saveExisting(self, result): + values = self.memdB.df # get save result - result = self.memDB.series_service.get_series_by_id_quint(variable, method, proc_level) + # set in df - # save(values, result) - self.memDB.series_service. + values["resultid"]=result.ResultID + # save(values) + self.memDB.series_service.upsert_values(values) + #insert new annotations + self.add_annotations(self.memDB.annotation_list) return result - # new series - - def createResult(self, var, meth, proc): - #if result does not exist - #create Action : of type "derivation" - #create Actionby - #create FeatureAction( using current sampling feature id - #create TimeSeriesResult- this should also contain all of the stuff for the Result + def getResult(self, var, meth, proc, action, actionby): # copy old + result = self.memDB.series_service.get_series(self.memDB.df["resultid"][0]) + self.memDB.series_service._session.expunge(result) + # change var, meth proc, in df #intend ts, agg stat - Result = None - return self.updateResult(Result) + result.VariableID = var.VariableID + result.VariableObj = var + result.ProcessingLevelID = proc.ProcessingLevelID + result.ProcessingLevelObj = proc + + #if result does not exist + + if self.memDB.series_service.resultExists(result): + pass + #create Action : of type "derivation" + #create Actionby + #create FeatureAction( using current sampling feature id + #create TimeSeriesResult- this should also contain all of the stuff for the Result + + + + return self.updateResult(result) def updateResult(self, Result): # get pd + values = self.memDB.df # get result # update count, dates, return Result @@ -565,6 +597,8 @@ def add_annotations(self, annolist): + + # # def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): # """ diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 63ee9a1..0965760 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -66,12 +66,13 @@ def get_used_variables(self): # Query DetailedResultInfo/series object is for Display purposes - def get_all_series(self): + def get_all_series(self, sitePid = None): """ Returns all series as a modelObject :return: List[Series] """ setSchema(self._session_factory.engine) + return self.read.getDetailedResultInfo('Time Series Coverage') def get_series(self, series_id=None): @@ -319,7 +320,7 @@ def get_method_by_code(self, method_code): # Series Catalog methods - def get_series_by_id_quint(self, result): + def resultExists(self, result): """ :param site_id: @@ -329,29 +330,29 @@ def get_series_by_id_quint(self, result): :param qcl_id: :return: Series """ - #unique Result - #FeatureActionID, ResultTypeCV, VariableID, UnitsID, ProcessingLevelID, SampledMediumCV + # unique Result + # FeatureActionID, ResultTypeCV, VariableID, UnitsID, ProcessingLevelID, SampledMediumCV try: # return self._edit_session.query(Results).filter_by( # VariableID=var_id, MethodID=method_id, # AnnotationID=qcl_id).first() - (ret, ), = self._session.query(exists(). - where(Results.FeatureActionID == result.FeatureActionID). - where(Results.ResultTypeCV == result.ResultTypeCV). + res = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV). where(Results.VariableID == result.VariableID). where(Results.UnitsID == result.UnitsID). where(Results.ProcessingLevelID == result.ProcessingLevelID). - wehre(Results.SampledMediumCV == result.SampledMediumCV) - + where(Results.SampledMediumCV == result.SampledMediumCV) + ) + # where(Results.FeatureActionID == result.FeatureActionID). - ) - return ret + return res except: return None + + def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass @@ -402,13 +403,13 @@ def get_all_values_df(self): query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, params=query.params) - columns = list(data) + #columns = list(data) # columns.insert(0, columns.pop(columns.index("DataValue"))) # columns.insert(1, columns.pop(columns.index("ValueDateTime"))) #columns.insert(2, columns.pop(columns.index("QualifierID"))) - data = data.ix[:, columns] + #data = data.ix[:, columns] return data.set_index(data['ValueDateTime']) # q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) # query = q.statement.compile(dialect = self._session_factory.engine.dialect) @@ -596,29 +597,29 @@ def get_current_time_and_utcoffset(self): # raise Exception("Series does not exist, unable to save. Please select 'Save As'") # # - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.info(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - logger.info("A new series was added to the database, series id: "+str(series.id)) - return True + # def save_new_series(self, series, dvs): + # """ Create as a new catalog entry + # :param series: + # :param data_values: + # :return: + # """ + # # Save As case + # if self.series_exists(series): + # msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" + # logger.info(msg) + # raise Exception(msg) + # else: + # try: + # self._edit_session.add(series) + # self._edit_session.commit() + # self.save_values(dvs) + # #self._edit_session.add_all(dvs) + # except Exception as e: + # self._edit_session.rollback() + # raise e + # + # logger.info("A new series was added to the database, series id: "+str(series.id)) + # return True def update_values(self, updates): @@ -706,7 +707,7 @@ def create_new_series(self, data_values, site_id, variable_id, method_id, source series.source_id = source_id series.quality_control_level_id = qcl_id - return self.create_service.createResult(series) + return self.create_service.getResult(series) def create_method(self, description, link): """ @@ -777,6 +778,23 @@ def create_annotation(self, code, text, link=None): return self.create_annotation_by_anno(annotation) + def add_annotations(self, anno_list): + try: + #tablename = TimeSeriesResultValueAnnotations.__tablename__ + #print ("I am TS saving name the table name", tablename) + anno_list.to_sql(name="TimeSeriesResultValueAnnotations", + schema=TimeSeriesResultValueAnnotations.__table_args__['schema'], + if_exists='append', + chunksize=1000, + con=self._session_factory.engine, + index=False) + self._session.commit() + + return anno_list + except Exception as e: + print(e) + return None + def get_vertical_datum_cvs(self): return self.read.getCVs(type="Elevation Datum") @@ -834,10 +852,21 @@ def get_all_annotations(self): def get_annotations_by_result(self, resultid): setSchema(self._session_factory.engine) - ids = [x[0] for x in self.read._session.query(TimeSeriesResultValues.ValueID)\ - .filter(TimeSeriesResultValues.ResultID == resultid).all()] - return self.read._session.query(TimeSeriesResultValueAnnotations)\ - .filter(TimeSeriesResultValueAnnotations.ValueID.in_(ids)).all() + + # ids = [x[0] for x in self.read._session.query(TimeSeriesResultValues.ValueID)\ + # .filter(TimeSeriesResultValues.ResultID == resultid).all()] + # q = self.read._session.query(TimeSeriesResultValueAnnotations)\ + # .filter(TimeSeriesResultValueAnnotations.ValueID.in_(ids)).all() + + q =self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, + TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime)\ + .filter(TimeSeriesResultValues.ResultID == resultid)\ + .filter(TimeSeriesResultValueAnnotations.ValueID == TimeSeriesResultValues.ValueID) + + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, con=self._session_factory.engine, + params=query.params) + return data def get_aggregation_statistic(self): return self.read.getCVs(type="aggregationstatistic") diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index f5aa40b..ec16828 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -191,11 +191,11 @@ def test_get_series_by_id(self): assert series.id == db_series.id def test_get_series_by_id_quint(self): - assert self.series_service.get_series_by_id_quint(10, 10, 10, 10, 10) == None + assert self.series_service.resultExists(10, 10, 10, 10, 10) == None series = test_util.add_series(self.session) - db_series = self.series_service.get_series_by_id_quint(series.site_id, series.variable_id, series.method_id, - series.source_id, series.quality_control_level_id) + db_series = self.series_service.resultExists(series.site_id, series.variable_id, series.method_id, + series.source_id, series.quality_control_level_id) assert series.id == db_series.id From 458b1cfa26d26794fa181d0d98badfbcb5b5cda1 Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 17:29:59 -0700 Subject: [PATCH 108/158] update results for existing series --- odmtools/controller/pageExisting.py | 13 +++++++------ odmtools/odmservices/series_service.py | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 841fc0e..f13924a 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -72,13 +72,14 @@ def getSeries(self): def initTable(self, dbservice, site_id): """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector""" - - seriesColumns = [clsExisting.ColumnDefn(key, align="left", - minimumWidth=-1, valueGetter=value, - stringConverter= '%Y-%m-%d %H:%M:%S' if 'date' in key.lower() else '%s') - for key, value in returnDict().iteritems()] + objects = dbservice.get_all_series(siteid=site_id) + seriesColumns = [ + clsExisting.ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, + # stringConverter = '%s') + stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else '%s') + for key in objects[0].__dict__.keys()] self.pnlExisting.olvSeriesList.SetColumns(seriesColumns) - objects = dbservice.get_series_by_site(site_id=site_id) + # objects = dbservice.get_series_by_site(site_id=site_id) self.pnlExisting.olvSeriesList.SetObjects(objects) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 0965760..5ebcf76 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -66,14 +66,16 @@ def get_used_variables(self): # Query DetailedResultInfo/series object is for Display purposes - def get_all_series(self, sitePid = None): + def get_all_series(self, siteid = None): """ Returns all series as a modelObject :return: List[Series] """ + setSchema(self._session_factory.engine) - return self.read.getDetailedResultInfo('Time Series Coverage') + + return self.read.getDetailedResultInfo('Time Series Coverage', sfID=siteid) def get_series(self, series_id=None): """ From f9d827a917ea36fb5ed9c24da365d19530b83feb Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Tue, 6 Dec 2016 17:43:19 -0700 Subject: [PATCH 109/158] header site info and variable and method info header print stuff --- odmtools/controller/frmSeriesSelector.py | 2 +- odmtools/gui/frmDataExport.py | 5 +- odmtools/odmservices/export_data.py | 85 ++++++++++++++++++++++++ odmtools/odmservices/service_manager.py | 3 +- 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 odmtools/odmservices/export_data.py diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index 16fea64..df9dc32 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -306,7 +306,7 @@ def onRightExData(self, event): full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) - series_id = self.tblSeries.GetSelectedObject().id + series_id = self.tblSeries.GetSelectedObject().ResultID self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) self.Close() diff --git a/odmtools/gui/frmDataExport.py b/odmtools/gui/frmDataExport.py index 800d950..2a1fe2e 100644 --- a/odmtools/gui/frmDataExport.py +++ b/odmtools/gui/frmDataExport.py @@ -79,7 +79,7 @@ def __init__(self, parent, series_id): self._init_ctrls(parent) sm = ServiceManager() - self.export_service = sm.get_export_service() + self.export_data = sm.get_export_service() def OnBtnOKButton(self, event): @@ -96,7 +96,8 @@ def OnBtnOKButton(self, event): full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) print full_path - self.export_service.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) + #self.export_service.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) + self.export_data.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) self.Close() dlg.Destroy() diff --git a/odmtools/odmservices/export_data.py b/odmtools/odmservices/export_data.py new file mode 100644 index 0000000..be26bf7 --- /dev/null +++ b/odmtools/odmservices/export_data.py @@ -0,0 +1,85 @@ +import csv +import xml.etree.cElementTree as ET +import datetime + +class ExportData(): + + def __init__(self, series_service): + self._series_service = series_service + self.dt_format_str = "%m/%d/%Y %I:%M:%S %p" + + def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, + src=False, qcl=False): + #series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) + + if series is None: + return False + + writer = csv.writer(open(filename, 'wb')) + plainWriter = open(filename, 'w') + print "filename: " + print filename + self.write_data_header(plainWriter, series, utc, site, var, offset, qual, src, qcl) + # for dv in series.data_values: + # self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) + + def export_data(self, series_ids, filename): + if series_ids is None: + return + + try: + with open(filename): + file_exists = True + except IOError: + file_exists = False + + if file_exists: + pass + + def write_data_header(self, plainWriter, series, utc, site, var, offset, qual, src, qcl): + self.write_warning_header(plainWriter) + self.write_site_information(plainWriter, series, site) + self.write_variable_and_method_information(plainWriter, series) + + + def write_site_information(self, plainWriter, series, site): + plainWriter.write('# Site Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# Network: TBD\n') + plainWriter.write('# SiteCode: '+str(series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureCode)+'\n') + plainWriter.write('# SiteName: ' + str(series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureName) + '\n') + plainWriter.write('# Latitude: ' + str(series.FeatureActionObj.SamplingFeatureObj.Latitude) + '\n') + plainWriter.write('# Longitude: ' + str(series.FeatureActionObj.SamplingFeatureObj.Longitude) + '\n') + plainWriter.write('# LatLonDatum: ' + 'TBD' + '\n') #FIX + plainWriter.write('# Elevation_m: ' + str(series.FeatureActionObj.SamplingFeatureObj.Elevation_m) + '\n') + plainWriter.write('# ElevationDatum: ' + str(series.FeatureActionObj.SamplingFeatureObj.ElevationDatumCV) + '\n') + plainWriter.write('# State: ' + 'TBD' + '\n') # FIX + plainWriter.write('# County: ' + 'TBD' + '\n') # FIX + plainWriter.write('# Comments: ' + 'TBD' + '\n') # FIX + plainWriter.write( + '# SiteType: ' + str(series.FeatureActionObj.SamplingFeatureObj.SiteTypeCV) + '\n') + plainWriter.write('#\n') + + def write_variable_and_method_information(self, plainWriter, series): + plainWriter.write('# Variable and Method Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# VariableCode: ' + str(series.VariableObj.VariableCode) + '\n') + plainWriter.write('# VariableName: ' + str(series.VariableObj.VariableName) + '\n') + + def write_warning_header(self, plainWriter): + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('# WARNING: The data are released on the condition that neither iUTAH nor any of its \n') + plainWriter.write('# participants may be held liable for any damages resulting from their use. The following \n') + plainWriter.write('# metadata describe the data in this file:\n') + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('#\n') + plainWriter.write('# Quality Control Level Information\n') + plainWriter.write('# -----------------------------------------------\n') + plainWriter.write('# These data have passed QA/QC procedures such as sensor calibration and\n') + plainWriter.write('# visual inspection and removal of obvious errors. These data are approved\n') + plainWriter.write('# by Technicians as the best available version of the data. See published\n') + plainWriter.write('# script for correction steps specific to this data series.\n') + plainWriter.write('#\n') \ No newline at end of file diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 450f04c..94f331d 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -9,6 +9,7 @@ from series_service import SeriesService from edit_service import EditService from export_service import ExportService +from export_data import ExportData from odmtools.controller import EditTools @@ -165,7 +166,7 @@ def get_record_service(self, script, series_id, connection): def get_export_service(self): - return ExportService(self.get_series_service()) + return ExportData(self.get_series_service()) ## ################### # private variables From 9637600bac561095c15a8325f829c0fc9f36534e Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Tue, 6 Dec 2016 18:41:02 -0700 Subject: [PATCH 110/158] Fixed the existing series not getting the selected series. Resolved some of the todos in the finished method in wizSave.py --- odmtools/controller/pageExisting.py | 7 ++++-- odmtools/gui/wizSave.py | 34 ++++++++++--------------- odmtools/odmservices/edit_service.py | 35 +++++++++++++++++--------- odmtools/odmservices/series_service.py | 2 +- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index f13924a..b910010 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -19,6 +19,8 @@ def __init__(self, parent, title, series_service , site): sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = sizer self.SetSizer(sizer) + self.site = site + self.series_service = series_service #self.series_service = series_service title = wx.StaticText(self, -1, title) @@ -66,9 +68,10 @@ def enableButtons(self, isEnabled): self.pnlExisting.rbOriginal.Enable(isEnabled) self.pnlExisting.lblOverlap.Enable(isEnabled) - def getSeries(self): + def get_selected_series(self): selectedObject = self.pnlExisting.olvSeriesList.GetSelectedObject() - return selectedObject.method, selectedObject.quality_control_level, selectedObject.variable + result = self.series_service.get_series(selectedObject.ResultID) + return result.FeatureActionObj.ActionObj.MethodObj, result.ProcessingLevelObj, result.VariableObj def initTable(self, dbservice, site_id): """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector""" diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index fab74a8..6d13f3b 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -262,7 +262,7 @@ def get_metadata(self): elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): # selected an existing series - method, processing_level, variable = self.pgExisting.getSeries() + method, processing_level, variable = self.pgExisting.get_selected_series() # Create action action = Actions() @@ -451,42 +451,34 @@ def on_wizard_finished(self, event): else: result = self.record_service.saveAs(Variable, Method, QCL, True) ''' - - - #TODO: move all of this stuff into the edit_service file - # Create action - new_result = self.series_service.getResult(var=variable, meth=method, proc=proc_level) - - # action = self.series_service.create.createAction(action) - affiliation = self.action_page.get_affiliation() - new_action_by = ActionBy() - new_action_by.ActionID = action.ActionID - new_action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() - new_action_by.AffiliationID = affiliation.AffiliationID - new_action_by.AffiliationObj = affiliation + action_by = ActionBy() + action_by.ActionID = action.ActionID + action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() + action_by.AffiliationID = affiliation.AffiliationID + action_by.AffiliationObj = affiliation - #TODO end + # result = self.series_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) + result = self.record_service._edit_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) try: if rbSave: result = self.record_service.save() elif rbSaveAsNew: - #TODO send in Action, and Actionby - result = self.record_service.save_as(variable, method, proc_level) + result = self.record_service.saveAs(variable=variable, method=method, proc_level=proc_level, + action=action, action_by=action_by) elif rbSaveAsExisting: if overwrite: - #TODO send in just the result - result = self.record_service.save_existing(variable, method, proc_level) + result = self.record_service.saveExisting(result=result) elif append: #TODO send in just the result #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): #TODO if i require that original or new is selected I can call once with overwrite = original if original: - result = self.record_service.save_appending(variable, method, proc_level, overwrite = False) + result = self.record_service.saveAppend(result=result, overwrite=False) elif new: - result = self.record_service.save_appending(variable, method, proc_level, overwrite = True) + result = self.record_service.saveAppend(result=result, overwrite=True) Publisher.sendMessage("refreshSeries") diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index c14ccdc..7f5b999 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -9,6 +9,7 @@ import pandas as pd import datetime import numpy as np +from odm2api.ODM2.models import * import logging from odmtools.common.logger import LoggerTool @@ -549,28 +550,38 @@ def saveExisting(self, result): self.add_annotations(self.memDB.annotation_list) return result - def getResult(self, var, meth, proc, action, actionby): # copy old result = self.memDB.series_service.get_series(self.memDB.df["resultid"][0]) - self.memDB.series_service._session.expunge(result) + sampling_feature = result.FeatureActionObj.SamplingFeatureObj + # self.memDB.series_service._session.expunge(result) # change var, meth proc, in df #intend ts, agg stat + if var: + result.VariableID = var.VariableID + result.VariableObj = var - result.VariableID = var.VariableID - result.VariableObj = var - result.ProcessingLevelID = proc.ProcessingLevelID - result.ProcessingLevelObj = proc + if proc: + result.ProcessingLevelID = proc.ProcessingLevelID + result.ProcessingLevelObj = proc - #if result does not exist + if meth: + result.FeatureActionObj.ActionObj.MethodID = meth.MethodID + result.FeatureActionObj.ActionObj.MethodObj = meth + #if result does not exist if self.memDB.series_service.resultExists(result): - pass - #create Action : of type "derivation" - #create Actionby - #create FeatureAction( using current sampling feature id - #create TimeSeriesResult- this should also contain all of the stuff for the Result + action.ActionTypeCV = "derivation" + # create Actionby done + + # create FeatureAction (using current sampling feature id) + feature_action = FeatureActions() + feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID + feature_action.SamplingFeatureObj = sampling_feature + + # create TimeSeriesResult - this should also contain all of the stuff for the Result + series = TimeSeriesResults() diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 5ebcf76..51dfbd1 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -88,7 +88,7 @@ def get_series(self, series_id=None): # print e # return None setSchema(self._session_factory.engine) - return self.read.getResults(ids=[series_id])[0] + return self.read.getResults(ids=[str(series_id)])[0] # Query result objects for data purposes def get_result_dates(self, result_id): From 984b0fd8eb186a0ac03dedc37a9ff20ec1a2aa82 Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 21:08:56 -0700 Subject: [PATCH 111/158] update series selector order --- odmtools/controller/frmSeriesSelector.py | 5 +++- odmtools/controller/olvSeriesSelector.py | 23 +++--------------- odmtools/controller/pageExisting.py | 5 ++-- odmtools/odmdata/__init__.py | 31 ++++++++++++------------ 4 files changed, 25 insertions(+), 39 deletions(-) diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index 16fea64..1aef6c6 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -9,6 +9,7 @@ from odmtools.common.logger import LoggerTool from odmtools.odmdata import MemoryDatabase +from odmtools.odmdata import returnDict from odmtools.view import clsSeriesSelector # tool = LoggerTool() @@ -71,7 +72,8 @@ def initTableSeries(self): self.memDB.set_series_service(self.series_service) object = self.series_service.get_all_series() - cols = object[0].__dict__.keys() + #cols = object[0].__dict__.keys() + cols = returnDict() self.tblSeries._buildColumns(cols) if object: self.tblSeries.SetObjects(object) @@ -94,6 +96,7 @@ def refreshTableSeries(self, db): self.memDB.set_series_service(db) object = self.series_service.get_all_series() #checkedObjs = self.tblSeries.GetCheckedObjects() + idList = [x.id for x in self.tblSeries.modelObjects] for x in object: diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 9ab20db..8d581dd 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -14,26 +14,9 @@ logger =logging.getLogger('main') OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() -from collections import OrderedDict -# def returnDict(): -# keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', -# 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', -# 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', -# 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', -# 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' -# ] -# values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', -# 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', -# 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', -# 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', -# 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', -# 'end_date_time_utc', 'value_count' -# ] -# return OrderedDict(zip(keys, values)) - - - -# def returnDict(): + + +# class clsSeriesTable(FastObjectListView): def __init__(self, *args, **kwargs): diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index f13924a..d48a6b0 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -77,9 +77,8 @@ def initTable(self, dbservice, site_id): clsExisting.ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, # stringConverter = '%s') stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else '%s') - for key in objects[0].__dict__.keys()] + # for key in objects[0].__dict__.keys()] + for key in returnDict()] self.pnlExisting.olvSeriesList.SetColumns(seriesColumns) - # objects = dbservice.get_series_by_site(site_id=site_id) - self.pnlExisting.olvSeriesList.SetObjects(objects) diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index 9c64e27..f8f5003 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -8,25 +8,26 @@ from collections import OrderedDict def returnDict(): - keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) + keys = ['ResultID', 'SamplingFeatureCode', 'SamplingFeatureName', 'MethodCode', 'MethodName', 'VariableCode', 'VariableNameCV', 'ProcessingLevelCode','ProcessingLevelDefinition', 'UnitsName', 'ValueCount', 'BeginDateTime', 'EndDateTime'] + # keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', + # 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', + # 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', + # 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', + # 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' + # ] + # values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', + # 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', + # 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', + # 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', + # 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', + # 'end_date_time_utc', 'value_count' + # ] + return OrderedDict(zip(keys, keys)) __all__=[ #'SessionFactory', 'refreshDB', 'change_schema', - 'returnDict', + #'returnDict', #'ODM', 'MemoryDatabase', 'returnDict' From 06a73b643f59c798a7f71b4deb12ab1d20871bea Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 6 Dec 2016 21:53:38 -0700 Subject: [PATCH 112/158] upate saving functions --- odmtools/controller/frmSeriesSelector.py | 4 +-- odmtools/odmservices/edit_service.py | 44 +++++++++++++++--------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index ccb4897..bac9e29 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -188,8 +188,8 @@ def OnTableRightDown(self, event): # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) # TODO @jmeline will refresh and clear selected as an enhancement - #self.Bind(wx.EVT_MENU, self.onRightRefresh, popup_menu.Append(popup_series_refresh, 'Refresh')) - #self.Bind(wx.EVT_MENU, self.onRightClearSelected, popup_menu.Append(popup_series_refresh, 'Clear Selected')) + + popup_menu.AppendSeparator() self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 7f5b999..43f617a 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -487,6 +487,7 @@ def save(self, result=None): if not result: result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) # update result + result.ValueCount = 0 self.updateResult(result) # upsert values self.memDB.series_service.upsert(values) @@ -508,6 +509,7 @@ def saveAppend(self, result, overwrite=True): # count = overlap calc count = self.overlapcalc() # set value count = res.vc+valuecount-count + result.ValueCount = result.ValueCount+ vc -count # insert values self.memDB.series_service.upsert_values(values) # save new annotations @@ -515,16 +517,30 @@ def saveAppend(self, result, overwrite=True): return result + def saveExisting(self, result): + values = self.memdB.df + # get save result + + # set in df + values["resultid"]=result.ResultID + + # save(values) + self.save(result) + #self.memDB.series_service.upsert_values(values) + + return result + def saveAs(self, variable, method, proc_level, action, action_by): + #save as new series values = self.memDB.df # get all annotations for series annolist= self.memDB.series_service.get_annotations_by_result(values["resultid"][0]) annolist['valueid'] # create series - result= self.getResult(variable, method, proc_level) - # update result - self.updateResult(result) + result = self.getResult(variable, method, proc_level) + result.ValueCount = 0 + # set in df values["resultid"] = result.ResultID # insert values @@ -538,18 +554,6 @@ def saveAs(self, variable, method, proc_level, action, action_by): return result - def saveExisting(self, result): - values = self.memdB.df - # get save result - - # set in df - values["resultid"]=result.ResultID - # save(values) - self.memDB.series_service.upsert_values(values) - #insert new annotations - self.add_annotations(self.memDB.annotation_list) - return result - def getResult(self, var, meth, proc, action, actionby): # copy old @@ -580,18 +584,24 @@ def getResult(self, var, meth, proc, action, actionby): feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID feature_action.SamplingFeatureObj = sampling_feature - # create TimeSeriesResult - this should also contain all of the stuff for the Result + # create TimeSeriesResult - this should also contain all of the stuff for the Result (hopefully the result will actually be of type, TimeSeriesResult) series = TimeSeriesResults() return self.updateResult(result) + form = "%Y-%m-%d %H:%M:%S" def updateResult(self, Result): + form = "%Y-%m-%d %H:%M:%S" # get pd values = self.memDB.df - # get result + # update count, dates, + Action = Result.FeatureActionObj.ActionObj + Action.BeginDateTime= datetime.datetime.strptime(str(np.min(values['valuedatetime'])), form) + Action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) + Result.ValueCount = len(values) return Result def overlapcalc(self): From 8e9b140a0bc9abba4be3219c3514aad481f8c931 Mon Sep 17 00:00:00 2001 From: stephanie Date: Wed, 7 Dec 2016 02:58:04 -0700 Subject: [PATCH 113/158] improve editing functions - update values, upsert values --- odmtools/controller/logicEditTools.py | 22 +++--- odmtools/controller/pageExisting.py | 4 +- odmtools/gui/wizSave.py | 14 ++-- odmtools/odmservices/edit_service.py | 29 +++---- odmtools/odmservices/series_service.py | 70 +++++++++++------ odmtools/odmservices/to_sql_newrows.py | 101 +++++++++++++------------ 6 files changed, 135 insertions(+), 105 deletions(-) diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index 47613ba..3c29e4b 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -222,7 +222,7 @@ def restore(self): self._script("edit_service.restore()\n", 'black') Publisher.sendMessage("scroll") - def saveFactory(self, var=None, method=None, qcl=None): + def saveFactory(self, var=None, method=None, qcl=None, action= None, actionby= None): """ :param var: @@ -236,7 +236,7 @@ def saveFactory(self, var=None, method=None, qcl=None): values['method'] = ("new_method" if method else None) values['qcl'] = ("new_qcl" if qcl else None) #values['override'] = override - return values['var'], values['method'], values['qcl']#, values['isSave'] + return values['var'], values['method'], values['qcl'], action, actionby#, values['isSave'] def save(self, var=None, method=None, qcl=None): @@ -257,7 +257,7 @@ def save(self, var=None, method=None, qcl=None): Publisher.sendMessage("scroll") return result - def save_as(self, var=None, method=None, qcl=None): + def save_as(self, var=None, method=None, qcl=None, action = None, actionby = None): """ :param var: @@ -266,16 +266,16 @@ def save_as(self, var=None, method=None, qcl=None): :param override: :return: """ - result = self._edit_service.save_as(var=var, method=method, qcl=qcl) + result = self._edit_service.save_as(var=var, method=method, qcl=qcl, action = action, actionby = actionby) if self._record: self._script( - "edit_service.save_as(%s, %s, %s)\n" % (self.saveFactory(var, method, qcl)), + "edit_service.save_as(%s, %s, %s, %s, %s)\n" % (self.saveFactory(var, method, qcl, action, actionby)), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") return result - def save_appending(self, var = None, method =None, qcl = None, overwrite = False): + def save_appending(self, result = None, overwrite = False): """ :param var: @@ -284,14 +284,14 @@ def save_appending(self, var = None, method =None, qcl = None, overwrite = False :param override: :return: """ - result = self._edit_service.save_appending(var=var, method=method, qcl=qcl, overwrite= overwrite) + result = self._edit_service.save_appending(result= result, overwrite= overwrite) if result: print "Save worked!" if self._record: self._script( - "edit_service.save_appending(%s, %s, %s, " % self.saveFactory(var, method, qcl)+str(overwrite )+")\n", + "edit_service.save_appending(%s, %s)\n" % (result, str(overwrite )), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") @@ -302,7 +302,7 @@ def save_appending(self, var = None, method =None, qcl = None, overwrite = False return result - def save_existing(self, var=None, method=None, qcl=None): + def save_existing(self, result=None): """ :param var: @@ -311,14 +311,14 @@ def save_existing(self, var=None, method=None, qcl=None): :param override: :return: """ - result = self._edit_service.save_existing(var=var, method=method, qcl=qcl) + result = self._edit_service.save_existing(result = result) if result: print "Save worked!" if self._record: self._script( - "edit_service.save_existing(%s, %s, %s)\n" % (self.saveFactory(var, method, qcl)), + "edit_service.save_existing(%s)\n" % (result), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 7f2fcf4..ef04145 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -70,8 +70,8 @@ def enableButtons(self, isEnabled): def get_selected_series(self): selectedObject = self.pnlExisting.olvSeriesList.GetSelectedObject() - result = self.series_service.get_series(selectedObject.ResultID) - return result.FeatureActionObj.ActionObj.MethodObj, result.ProcessingLevelObj, result.VariableObj + result = selectedObject.ResultObj#self.series_service.get_series(selectedObject.ResultID) + return result.FeatureActionObj.ActionObj.MethodObj, result.ProcessingLevelObj, result.VariableObj, result def initTable(self, dbservice, site_id): """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector""" diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 6d13f3b..614f5fc 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -262,7 +262,7 @@ def get_metadata(self): elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): # selected an existing series - method, processing_level, variable = self.pgExisting.get_selected_series() + method, processing_level, variable, result = self.pgExisting.get_selected_series() # Create action action = Actions() @@ -460,25 +460,27 @@ def on_wizard_finished(self, event): action_by.AffiliationObj = affiliation # result = self.series_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) - result = self.record_service._edit_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) + result = self.pgExisting.pnlExisting.olvSeriesList.GetSelectedObject().ResultObj + + #result = self.record_service._edit_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) try: if rbSave: result = self.record_service.save() elif rbSaveAsNew: - result = self.record_service.saveAs(variable=variable, method=method, proc_level=proc_level, + result = self.record_service.save_as(variable=variable, method=method, proc_level=proc_level, action=action, action_by=action_by) elif rbSaveAsExisting: if overwrite: - result = self.record_service.saveExisting(result=result) + result = self.record_service.save_existing(result=result) elif append: #TODO send in just the result #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): #TODO if i require that original or new is selected I can call once with overwrite = original if original: - result = self.record_service.saveAppend(result=result, overwrite=False) + result = self.record_service.save_appending(result=result, overwrite=False) elif new: - result = self.record_service.saveAppend(result=result, overwrite=True) + result = self.record_service.save_appending(result=result, overwrite=True) Publisher.sendMessage("refreshSeries") diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 43f617a..e82aaee 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -483,20 +483,24 @@ def restore(self): self.reset_filter() def save(self, result=None): - values = self.memDB.df + values = self.memDB.getDataValuesDF() + if not result: result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) + else: + values["resultid"] = result.ResultID + # update result result.ValueCount = 0 self.updateResult(result) # upsert values - self.memDB.series_service.upsert(values) + self.memDB.series_service.upsert_values(values) # save new annotations self.add_annotations(self.memDB.annotation_list) return result - def saveAppend(self, result, overwrite=True): - values = self.memDB.df + def save_appending(self, result, overwrite=True): + values = self.memDB.getDataValuesDF() # get save result @@ -517,22 +521,21 @@ def saveAppend(self, result, overwrite=True): return result - def saveExisting(self, result): - values = self.memdB.df + def save_existing(self, result): + #values = self.memDB.getDataValuesDF() # get save result # set in df - values["resultid"]=result.ResultID + #values["resultid"]=result.ResultID # save(values) self.save(result) - #self.memDB.series_service.upsert_values(values) return result - def saveAs(self, variable, method, proc_level, action, action_by): + def save_as(self, variable, method, proc_level, action, action_by): #save as new series - values = self.memDB.df + values = self.memDB.getDataValuesDF() # get all annotations for series annolist= self.memDB.series_service.get_annotations_by_result(values["resultid"][0]) annolist['valueid'] @@ -555,9 +558,9 @@ def saveAs(self, variable, method, proc_level, action, action_by): return result def getResult(self, var, meth, proc, action, actionby): - + values = self.memDB.getDataValuesDF() # copy old - result = self.memDB.series_service.get_series(self.memDB.df["resultid"][0]) + result = self.memDB.series_service.get_series(values["resultid"][0]) sampling_feature = result.FeatureActionObj.SamplingFeatureObj # self.memDB.series_service._session.expunge(result) @@ -595,7 +598,7 @@ def getResult(self, var, meth, proc, action, actionby): def updateResult(self, Result): form = "%Y-%m-%d %H:%M:%S" # get pd - values = self.memDB.df + values = self.memDB.getDataValuesDF() # update count, dates, Action = Result.FeatureActionObj.ActionObj diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 51dfbd1..5298bb9 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -3,7 +3,7 @@ from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -from odmtools.odmservices.to_sql_newrows import clean_df_db_dups, delete, update +from odmtools.odmservices.to_sql_newrows import get_insert, get_delete, get_update import datetime from odmtools.common.logger import LoggerTool import pandas as pd @@ -624,38 +624,39 @@ def get_current_time_and_utcoffset(self): # return True - def update_values(self, updates): - ''' - updates : time series result values, pandas dataframe - ''' - setSchema(self.mem_service._session_factory.engine) - stmt = (TimeSeriesResultValues.__table__.update(). - where(TimeSeriesResultValues.ValueDateTime == bindparam('id')). - values(datavalue=bindparam('value')) - ) - self.create._session.execute(stmt, updates["datavalue"].to_dict(orient='dict')) - #self.mem_service._session.query(TSRV).filter_by - # self.updateDF() def insert_annotations(self, annotations): annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) + def _get_df_query(self, values): + + resid = str(values['resultid'][0]) + sd = values['valuedatetime'].min() + ed = values['valuedatetime'].max() + q = self.read._session.query(TimeSeriesResultValues).\ + filter(TimeSeriesResultValues.ResultID == resid)#.\ + #filter(TimeSeriesResultValues.ValueDateTime.between(sd, ed)) + return q.statement.compile(dialect=self._session_factory.engine.dialect) + def upsert_values(self, values): setSchema(self._session_factory.engine) - newvals= clean_df_db_dups(df = values, dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_continuous_col="valuedatetime", filter_categorical_col="resultid") - self.insert_values(newvals) - delvals = delete(df = values, dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_continuous_col = "valuedatetime", filter_categorical_col = "resultid") - self.delete_dvs(delvals["valuedatetime"]) + query = self._get_df_query(values) + newvals= get_insert(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + if not newvals.empty: + self.insert_values(newvals) + delvals = get_delete(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + if not delvals.empty: + self.delete_dvs(delvals["valuedatetime"].tolist()) - upvals = update(df = values,dup_cols = ["valuedatetime", "resultid"], tablename="timeseriesresultvalues", engine = self._session_factory.engine, - filter_continuous_col="valuedatetime", filter_categorical_col="resultid") - self.update(upvals) + upvals = get_update(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + if not upvals.empty: + self.update_values(upvals) + + self._session.commit() def insert_values(self, values): """ @@ -663,8 +664,29 @@ def insert_values(self, values): :param values: pandas dataframe :return: """ - values.to_sql(name="timeseriesresultvalues", if_exists='append', con=self._session_factory.engine, index=False) -# + values.to_sql(name="TimeSeriesResultValues", + schema=TimeSeriesResultValues.__table_args__['schema'], + if_exists='append', + chunksize=1000, + con=self._session_factory.engine, + index=False) + + + def update_values(self, updates): + ''' + updates : time series result values, pandas dataframe + ''' + setSchema(self._session_factory.engine) + + stmt = (TimeSeriesResultValues.__table__.update(). + where(TimeSeriesResultValues.ValueDateTime == bindparam('id')). + values(datavalue=bindparam('value')) + ) + update_list = [{"value": row["datavalue"], "id": index.to_pydatetime()} for index, row in updates.iterrows()] + # update_list = {'value':updates["datavalue"].tolist(), 'id':updates.index.to_pydatetime().tolist()} + vals = self.create._session.execute(stmt, update_list) + + # def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): # """ # diff --git a/odmtools/odmservices/to_sql_newrows.py b/odmtools/odmservices/to_sql_newrows.py index 039583c..e8cac3c 100644 --- a/odmtools/odmservices/to_sql_newrows.py +++ b/odmtools/odmservices/to_sql_newrows.py @@ -10,50 +10,53 @@ os.path.dirname(os.path.abspath(__file__)) -def get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): - - args = 'SELECT %s FROM %s' % (', '.join(['"{0}"'.format(col) for col in dup_cols]), tablename) - args_contin_filter, args_cat_filter, args_eq_filter = None, None, None - if filter_continuous_col is not None: - if df[filter_continuous_col].dtype == 'datetime64[ns]': - args_contin_filter = """ "%s" BETWEEN Convert(datetime, '%s') - AND Convert(datetime, '%s')""" % (filter_continuous_col, - df[filter_continuous_col].min(), - df[filter_continuous_col].max()) - - if filter_categorical_col is not None: - args_cat_filter = ' "%s" in(%s)' % (filter_categorical_col, - ', '.join(["'{0}'".format(value) for value in - df[filter_categorical_col].unique()])) - # if filter_equal_col is not None: - # args_eq_filter = ' "%s" = %(s)' %(filter_categorical_col, df) - - if args_contin_filter and args_cat_filter: - args += ' Where ' + args_contin_filter + ' AND' + args_cat_filter - elif args_contin_filter: - args += ' Where ' + args_contin_filter - elif args_cat_filter: - args += ' Where ' + args_cat_filter - - return args - -def delete(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): - query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) +# def get_df_query(df, tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): +# +# +# +# args = 'SELECT %s FROM %s' % (', '.join(['"{0}"'.format(col) for col in dup_cols]), tablename) +# args_contin_filter, args_cat_filter, args_eq_filter = None, None, None +# if filter_continuous_col is not None: +# if df[filter_continuous_col].dtype == 'datetime64[ns]': +# args_contin_filter = """ "%s" BETWEEN Convert(datetime, '%s') +# AND Convert(datetime, '%s')""" % (filter_continuous_col, +# df[filter_continuous_col].min(), +# df[filter_continuous_col].max()) +# +# if filter_categorical_col is not None: +# args_cat_filter = ' "%s" in(%s)' % (filter_categorical_col, +# ', '.join(["'{0}'".format(value) for value in +# df[filter_categorical_col].unique()])) +# # if filter_equal_col is not None: +# # args_eq_filter = ' "%s" = %(s)' %(filter_categorical_col, df) +# +# if args_contin_filter and args_cat_filter: +# args += ' Where ' + args_contin_filter + ' AND' + args_cat_filter +# elif args_contin_filter: +# args += ' Where ' + args_contin_filter +# elif args_cat_filter: +# args += ' Where ' + args_cat_filter +# +# return args + +def get_delete(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) df.drop_duplicates(dup_cols, keep='last', inplace=True) - df = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) - df = df[df['_merge'] == 'right_only'] - df.drop(['_merge'], axis=1, inplace=True) - return df - -def update(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): - query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) - #df.drop_duplicates(dup_cols, keep='last', inplace=True) - df = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) - #df = df[df['_merge'] == 'right_only'] - #df.drop(['_merge'], axis=1, inplace=True) - return df - -def clean_df_db_dups(df, tablename, engine, dup_cols=[], filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): + newdf = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + +def get_update(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) + #newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + test = newdf[newdf['datavalue_x'] != newdf['datavalue_y']] + return df[df['valuedatetime'].isin(test['valuedatetime'])] + +def get_insert(df, engine, query, dup_cols=[]): """ Remove rows from a dataframe that already exist in a database Required: @@ -71,12 +74,12 @@ def clean_df_db_dups(df, tablename, engine, dup_cols=[], filter_continuous_col=N Unique list of values from dataframe compared to database table """ - query = get_df_query(tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None) + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) df.drop_duplicates(dup_cols, keep='last', inplace=True) - df = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) - df = df[df['_merge'] == 'left_only'] - df.drop(['_merge'], axis=1, inplace=True) - return df + newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'left_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] def to_sql_newrows(df, pool_size, *args, **kargs): @@ -165,7 +168,7 @@ def setup(engine, tablename): print 'running test %s' % (str(i)) df = pd.DataFrame( np.random.randint(0, 500, size=(100000, 4)), columns=list('ABCD')) - df = clean_df_db_dups(df, TABLENAME, ENGINE, dup_cols=['A', 'B']) + df = get_insert(df, TABLENAME, ENGINE, dup_cols=['A', 'B']) print 'row count after drop db duplicates is now : %s' % (df.shape[0]) df.to_sql(TABLENAME, ENGINE, if_exists='append', index=False) end = timer() From a071d9a3ec6429bf448687dfec5f45abf716cf59 Mon Sep 17 00:00:00 2001 From: stephanie Date: Wed, 7 Dec 2016 03:41:29 -0700 Subject: [PATCH 114/158] update add point form --- odmtools/controller/frmAddPoints.py | 7 +++-- odmtools/controller/logicCellEdit.py | 29 +++--------------- odmtools/controller/olvAddPoint.py | 42 +++++++++++++------------- odmtools/odmdata/memory_database.py | 2 +- odmtools/odmservices/series_service.py | 5 +-- 5 files changed, 34 insertions(+), 51 deletions(-) diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py index c9c6a44..604cfe8 100644 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -123,7 +123,7 @@ def onInfoBtn(self, event): :return: """ self.checkIfEditing() - + #todo fran: update this for odm2 info. message = "DataValue: FLOAT\n" \ "Date: YYYY-MM-DD\n" \ "Time: HH:MM:SS\n" \ @@ -233,8 +233,9 @@ def parseTable(self): row[4] = point.censorCode row[5] = point.qualityCodeCV row[6] = point.timeAggInterval - row[7] = point.timeAggregationUnitID - row[8] = point.annotation + # row[7] = point.timeAggregationUnitID + row[7] = self.olv.cellEdit.timeAggretaionUnitChoices[point.timeAggregationUnitID] + row[8] = self.olv.cellEdit.qualifierChoices[ point.annotation] if point.annotation != 'NULL' else point.annotation row.extend([site_id, variable_id, method_id, organization_id, process_id]) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 1718c59..6eb1300 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -30,9 +30,9 @@ def __init__(self, parent, serviceManager, recordService): self.annotationChoices = [NULL] + ['SampleAnnotation1'] + ['SampleAnnotation2'] + ['SampleAnnotation3'] def fetch_annotations(self): - qualifierChoices = OrderedDict((x.AnnotationCode + ':' + x.AnnotationText, x.AnnotationID) + self.qualifierChoices = OrderedDict((x.AnnotationCode + ':' + x.AnnotationText, x.AnnotationID) for x in self.series_service.get_all_qualifiers() if x.AnnotationCode and x.AnnotationText) - qualifierCodeChoices = [NULL] + qualifierChoices.keys() + [NEW] + qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] return qualifierCodeChoices def fetchCensorCodeChoices(self): @@ -55,8 +55,8 @@ def fetchQualityCodeChoices(self): def fetchTimeUnitChoices(self): if not self.serviceManager: return [NULL] - units = self.series_service.read.getUnits() - return [NULL] + [unit.UnitsName for unit in units] + units = self.series_service.read.getUnits(type='time') + return {unit.UnitsName:unit.UnitsID for unit in units} """ -------------------- @@ -172,26 +172,7 @@ def imgGetterUTCOFFset(self, point): return "error" - def imgGetterValueAcc(self, point): - value = point.valueAccuracy - point.validValueAcc = False - if not value: - return "error" - - if value == NULL: - point.validValueAcc = True - return "check" - if isinstance(value, basestring): - for type in [int, float]: - try: - value = type(value) - if isinstance(value, type): - point.validValueAcc = True - return "check" - except ValueError: - continue - return "error" def imgGetterOffSetType(self, point): point.validOffSetType = False @@ -336,7 +317,7 @@ def setComboForQualityCodeColumn(self, olv, rowIndex, subItemIndex): return odcb def setComboForTimeAggregationUnitIDCreator(self, olv, rowIndex, subItemIndex): - customCombo = CustomComboBox(olv, choices=self.timeAggretaionUnitChoices, style=wx.CB_READONLY) + customCombo = CustomComboBox(olv, choices=self.timeAggretaionUnitChoices.keys(), style=wx.CB_READONLY) customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return customCombo diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index a9cdd8f..dbdaeab 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -60,41 +60,41 @@ def __init__(self, *args, **kwargs): FastObjectListView.__init__(self, *args, **kwargs) - cellEdit = CellEdit(self, self.serviceManager, self.recordService) + self.cellEdit = CellEdit(self, self.serviceManager, self.recordService) self.checkedObjects = [] # # Custom Image Getters - self.imgGetterDataValue = cellEdit.imgGetterDataValue - self.imgGetterDate = cellEdit.imgGetterDate - self.imgGetterTime = cellEdit.imgGetterTime - self.imgGetterCensorCode = cellEdit.imgGetterCensorCode - self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset - self.imgGetterQualityCode = cellEdit.imgGetterQualityCode - self.imgGetterTimeAggInterval = cellEdit.imgGetterTimeAggregationInterval - self.imgGetterTimeAggUnit = cellEdit.imgGetterTimeAggregationUnit + self.imgGetterDataValue = self.cellEdit.imgGetterDataValue + self.imgGetterDate = self.cellEdit.imgGetterDate + self.imgGetterTime = self.cellEdit.imgGetterTime + self.imgGetterCensorCode = self.cellEdit.imgGetterCensorCode + self.imgGetterUTCOffset = self.cellEdit.imgGetterUTCOFFset + self.imgGetterQualityCode = self.cellEdit.imgGetterQualityCode + self.imgGetterTimeAggInterval = self.cellEdit.imgGetterTimeAggregationInterval + self.imgGetterTimeAggUnit = self.cellEdit.imgGetterTimeAggregationUnit ## Custom Value Setters ## Sets the value, can modify rules for setting value - self.valueSetterDataValue = cellEdit.valueSetterDataValue - self.valueSetterUTCOffset = cellEdit.valueSetterUTCOffset + self.valueSetterDataValue = self.cellEdit.valueSetterDataValue + self.valueSetterUTCOffset = self.cellEdit.valueSetterUTCOffset ## Custom String Converters ## Changes how the string will appear in the cell after editing - self.localtime2Str = cellEdit.strConverterLocalTime - self.str2DataValue = cellEdit.strConverterDataValue - self.utcOffSet2Str = cellEdit.strConverterUTCOffset + self.localtime2Str = self.cellEdit.strConverterLocalTime + self.str2DataValue = self.cellEdit.strConverterDataValue + self.utcOffSet2Str = self.cellEdit.strConverterUTCOffset # self.offSetValue2Str = cellEdit.strConverterOffSetValue ## Custom CellEditors ## Custom cell editors for each cell - self.dateEditor = cellEdit.dateEditor - self.timeEditor = cellEdit.localTimeEditor - self.censorEditor = cellEdit.censorCodeEditor - self.valueDateTimeEditorCreator = cellEdit.valueDateTimeEditor - self.qualityCodeCreator = cellEdit.setComboForQualityCodeColumn - self.timeAggregationUnitIDCreator = cellEdit.setComboForTimeAggregationUnitIDCreator - self.annotationCreator = cellEdit.setComboForAnnotation + self.dateEditor = self.cellEdit.dateEditor + self.timeEditor = self.cellEdit.localTimeEditor + self.censorEditor = self.cellEdit.censorCodeEditor + self.valueDateTimeEditorCreator = self.cellEdit.valueDateTimeEditor + self.qualityCodeCreator = self.cellEdit.setComboForQualityCodeColumn + self.timeAggregationUnitIDCreator = self.cellEdit.setComboForTimeAggregationUnitIDCreator + self.annotationCreator = self.cellEdit.setComboForAnnotation self.SetEmptyListMsg("Add points either by csv or by adding a new row") self.AddNamedImages("error", x_mark_16.GetBitmap(), x_mark_32.GetBitmap()) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index fe399dc..68dc644 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -212,7 +212,7 @@ def addPoints(self, points): # todo: Add annotations } if point[8]: - self.updateFlag(point[1], self.series_service.get_annotation_by_code(point[8].split(':')[0]).AnnotationID) + self.updateFlag(point[1], self.series_service.get_annotation_by_id(point[8]).AnnotationID) setSchema(self.mem_service._session_factory.engine) self.mem_service._session.execute(stmt, vals) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 5298bb9..31d8251 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -686,7 +686,7 @@ def update_values(self, updates): # update_list = {'value':updates["datavalue"].tolist(), 'id':updates.index.to_pydatetime().tolist()} vals = self.create._session.execute(stmt, update_list) - + # def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): # """ # @@ -870,7 +870,8 @@ def get_quality_code(self): def get_annotation_by_code(self, code): return self.read.getAnnotations(codes=[code])[0] - + def get_annotation_by_id (self, id): + return self.read.getAnnotations(ids=[id])[0] def get_all_annotations(self): return self.read.getAnnotations(type=None) From f519169c869cfc02dab1d7adadfa9c116bfcfc58 Mon Sep 17 00:00:00 2001 From: stephanie Date: Wed, 7 Dec 2016 10:02:23 -0700 Subject: [PATCH 115/158] update caloverlap --- odmtools/controller/WizardActionController.py | 2 + odmtools/controller/logicCellEdit.py | 4 +- odmtools/controller/logicEditTools.py | 6 +- odmtools/gui/wizSave.py | 3 +- odmtools/odmdata/memory_database.py | 7 +- odmtools/odmservices/edit_service.py | 152 ++++++++++++------ odmtools/odmservices/series_service.py | 6 +- 7 files changed, 119 insertions(+), 61 deletions(-) diff --git a/odmtools/controller/WizardActionController.py b/odmtools/controller/WizardActionController.py index 44b2d0b..29edf21 100644 --- a/odmtools/controller/WizardActionController.py +++ b/odmtools/controller/WizardActionController.py @@ -46,6 +46,8 @@ def get_affiliation(self): return self.affiliations[index] + + if __name__ == '__main__': app = wx.App(False) controller = WizardActionController(None, None) diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 6eb1300..1afb0eb 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -40,7 +40,7 @@ def fetchCensorCodeChoices(self): return [NULL] series_service = self.serviceManager.get_series_service() - return [NULL] + [x.Term for x in series_service.get_censor_code_cvs()] + return [NULL] + [x.Name for x in series_service.get_censor_code_cvs()] def fetchQualityCodeChoices(self): """ @@ -50,7 +50,7 @@ def fetchQualityCodeChoices(self): return [NULL] series_service = self.serviceManager.get_series_service() - return [NULL] + [x.Term for x in series_service.get_quality_code()] + return [NULL] + [x.Name for x in series_service.get_quality_code()] def fetchTimeUnitChoices(self): if not self.serviceManager: diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index 3c29e4b..4f6ef75 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -257,7 +257,7 @@ def save(self, var=None, method=None, qcl=None): Publisher.sendMessage("scroll") return result - def save_as(self, var=None, method=None, qcl=None, action = None, actionby = None): + def save_as(self, variable=None, method=None, proc_level=None, action = None, action_by = None): """ :param var: @@ -266,10 +266,10 @@ def save_as(self, var=None, method=None, qcl=None, action = None, actionby = Non :param override: :return: """ - result = self._edit_service.save_as(var=var, method=method, qcl=qcl, action = action, actionby = actionby) + result = self._edit_service.save_as(variable=variable, method=method, proc_level=proc_level, action = action, action_by = action_by) if self._record: self._script( - "edit_service.save_as(%s, %s, %s, %s, %s)\n" % (self.saveFactory(var, method, qcl, action, actionby)), + "edit_service.save_as(%s, %s, %s, %s, %s)\n" % (self.saveFactory(variable, method, proc_level, action, action_by)), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 614f5fc..63995e0 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -272,6 +272,7 @@ def get_metadata(self): action.ActionFileLink = self.action_page.action_view.action_file_link_text_box.GetValue() action.MethodObj.OrganizationObj = affiliation.OrganizationObj action.BeginDateTime = self.currSeries.ResultDateTime + action.BeginDateTimeUTCOffset = self.currSeries.ResultDateTimeUTCOffset return site, variable, method, action, processing_level @@ -454,7 +455,7 @@ def on_wizard_finished(self, event): affiliation = self.action_page.get_affiliation() action_by = ActionBy() - action_by.ActionID = action.ActionID + #action_by.ActionID = action.ActionID action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() action_by.AffiliationID = affiliation.AffiliationID action_by.AffiliationObj = affiliation diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 68dc644..813fbe9 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -160,7 +160,7 @@ def chunking(self, data): #break into chunks to get around sqlite's restriction. allowing user to send in only 999 arguments at once - #TODO update to work with odm2 + def updateFlag(self, ids, value): @@ -211,8 +211,9 @@ def addPoints(self, points): "resultid":self.df["resultid"][0] # todo: Add annotations } - if point[8]: - self.updateFlag(point[1], self.series_service.get_annotation_by_id(point[8]).AnnotationID) + if point[8] != 'NULL': + print point[8] + self.updateFlag([point[1]], [point[8]]) setSchema(self.mem_service._session_factory.engine) self.mem_service._session.execute(stmt, vals) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index e82aaee..0227f0b 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -499,11 +499,13 @@ def save(self, result=None): self.add_annotations(self.memDB.annotation_list) return result + def save_existing(self, result): + result = self.save(result) + return result + def save_appending(self, result, overwrite=True): values = self.memDB.getDataValuesDF() - # get save result - # get value count vc = result.ValueCount # set in df @@ -511,25 +513,14 @@ def save_appending(self, result, overwrite=True): # update result self.updateResult(result) # count = overlap calc - count = self.overlapcalc() + count = self.overlapcalc(result, values, overwrite) # set value count = res.vc+valuecount-count - result.ValueCount = result.ValueCount+ vc -count + result.ValueCount = result.ValueCount + vc - count + self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) # insert values self.memDB.series_service.upsert_values(values) # save new annotations self.add_annotations(self.memDB.annotation_list) - return result - - - def save_existing(self, result): - #values = self.memDB.getDataValuesDF() - # get save result - - # set in df - #values["resultid"]=result.ResultID - - # save(values) - self.save(result) return result @@ -537,12 +528,11 @@ def save_as(self, variable, method, proc_level, action, action_by): #save as new series values = self.memDB.getDataValuesDF() # get all annotations for series - annolist= self.memDB.series_service.get_annotations_by_result(values["resultid"][0]) - annolist['valueid'] + annolist= self.memDB.series_service.get_annotations_by_result(str(values["resultid"][0])) + annolist['valueid']=None # create series - result = self.getResult(variable, method, proc_level) - result.ValueCount = 0 + result = self.getResult(variable, method, proc_level, action, action_by) # set in df values["resultid"] = result.ResultID @@ -557,14 +547,15 @@ def save_as(self, variable, method, proc_level, action, action_by): return result - def getResult(self, var, meth, proc, action, actionby): + def getResult(self, var, meth, proc, action, action_by): values = self.memDB.getDataValuesDF() # copy old - result = self.memDB.series_service.get_series(values["resultid"][0]) - sampling_feature = result.FeatureActionObj.SamplingFeatureObj - # self.memDB.series_service._session.expunge(result) + result = self.memDB.series_service.get_series(str(values["resultid"][0])) + result.ResultID = None + result.ResultUUID = None + - # change var, meth proc, in df #intend ts, agg stat + # change var, meth proc, in df #intend ts, agg sta if var: result.VariableID = var.VariableID result.VariableObj = var @@ -574,43 +565,98 @@ def getResult(self, var, meth, proc, action, actionby): result.ProcessingLevelObj = proc if meth: + action.MethodID = meth.MethodID result.FeatureActionObj.ActionObj.MethodID = meth.MethodID - result.FeatureActionObj.ActionObj.MethodObj = meth #if result does not exist - if self.memDB.series_service.resultExists(result): - action.ActionTypeCV = "derivation" - # create Actionby done - - # create FeatureAction (using current sampling feature id) - feature_action = FeatureActions() - feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID - feature_action.SamplingFeatureObj = sampling_feature - - # create TimeSeriesResult - this should also contain all of the stuff for the Result (hopefully the result will actually be of type, TimeSeriesResult) - series = TimeSeriesResults() - - - + if not self.memDB.series_service.resultExists(result): + try: + #create Action + action.ActionID = None + #action.MethodObj = None + action.ActionTypeCV = "Derivation" + self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) + self.memDB.series_service.read._session.expunge(action.MethodObj) + action = self.memDB.series_service.create.create(action) + print action + + + # create Actionby done + action_by.ActionID = action.ActionID + action_by= self.memDB.series_service.create.createActionby(action_by) + print action_by + + + # create FeatureAction (using current sampling feature id) + sampling_feature = result.FeatureActionObj.SamplingFeatureObj + self.memDB.series_service.read._session.expunge(result.FeatureActionObj.SamplingFeatureObj) + + feature_action = FeatureActions() + feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID + feature_action.ActionID = action.ActionID + feature_action.ActionObj = action + feature_action.SamplingFeatureObj = sampling_feature + feature_action = self.memDB.series_service.create.createFeatureAction(feature_action) + print feature_action + + # create TimeSeriesResult - this should also contain all of the stuff for the Result + time, offset = self.get_current_time_and_utcoffset() + + result.ValueCount = 0 + result.FeatureActionID = feature_action.FeatureActionID + result.ResultDateTime = time + result.ResultDateTimeUTCOffset = offset + result.FeatureActionObj= feature_action + self.memDB.series_service.read._session.expunge(result.ProcessingLevelObj) + self.memDB.series_service.read._session.expunge(result.VariableObj) + self.memDB.series_service.read._session.expunge(result) + + + result = self.memDB.series_service.create.createResult(result) + print result + except Exception as ex: + print ex return self.updateResult(result) - form = "%Y-%m-%d %H:%M:%S" - def updateResult(self, Result): + + def updateResult(self, result): form = "%Y-%m-%d %H:%M:%S" # get pd values = self.memDB.getDataValuesDF() # update count, dates, - Action = Result.FeatureActionObj.ActionObj - Action.BeginDateTime= datetime.datetime.strptime(str(np.min(values['valuedatetime'])), form) - Action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) - Result.ValueCount = len(values) - return Result + action = result.FeatureActionObj.ActionObj + action.BeginDateTime= datetime.datetime.strptime(str(np.min(values['valuedatetime'])), form) + action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) + result.ValueCount = len(values) + self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) + self.memDB.series_service.update.updateAction(actionID=action.ActionID, begin=action.BeginDateTime, end=action.EndDateTime) - def overlapcalc(self): - pass + return result + + def overlapcalc(self, result, values, overwrite): + form = "%Y-%m-%d %H:%M:%S" + + #is there any overlap + dbend = result.FeatureActionObj.ActionObj.EndDateTime + dfstart = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) + overlap = dbend>= dfstart + #number of overlapping values + overlapdf = values[(values["valuedatetime"]>= dfstart) & (values["valudatetime"]<= dbend)] + count =len(overlapdf) + #if not overwrite. remove any overlapping values from df + if overlap: + if not overwrite: + values = values[values["valuedatetime"] > dbend] + + # return the number of overlapping values + return count def add_annotations(self, annolist): + #match up with existing values and get value id + #get df with only ValueID and AnnotationID + #remove any duplicates + #save df to db pass @@ -810,3 +856,11 @@ def reconcile_dates(self, parent_series_id): pass + def get_current_time_and_utcoffset(self): + current_time = datetime.datetime.now() + utc_time = datetime.datetime.utcnow() + + difference_in_timezone = current_time - utc_time + offset_in_hours = difference_in_timezone.total_seconds() / 3600 + + return current_time, offset_in_hours \ No newline at end of file diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 31d8251..07318db 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -340,7 +340,7 @@ def resultExists(self, result): # return self._edit_session.query(Results).filter_by( # VariableID=var_id, MethodID=method_id, # AnnotationID=qcl_id).first() - res = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV). + ret = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV). where(Results.VariableID == result.VariableID). where(Results.UnitsID == result.UnitsID). where(Results.ProcessingLevelID == result.ProcessingLevelID). @@ -349,7 +349,7 @@ def resultExists(self, result): # where(Results.FeatureActionID == result.FeatureActionID). - return res + return ret.scalar() except: return None @@ -870,7 +870,7 @@ def get_quality_code(self): def get_annotation_by_code(self, code): return self.read.getAnnotations(codes=[code])[0] - def get_annotation_by_id (self, id): + def get_annotation_by_id(self, id): return self.read.getAnnotations(ids=[id])[0] def get_all_annotations(self): return self.read.getAnnotations(type=None) From 00af92df8cca3091cc16cf36cfbba451f38f8d1d Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Wed, 7 Dec 2016 10:30:54 -0700 Subject: [PATCH 116/158] csv header --- odmtools/odmservices/export_data.py | 202 +++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 21 deletions(-) diff --git a/odmtools/odmservices/export_data.py b/odmtools/odmservices/export_data.py index be26bf7..7d64d17 100644 --- a/odmtools/odmservices/export_data.py +++ b/odmtools/odmservices/export_data.py @@ -16,13 +16,15 @@ def export_series_data(self, series_id, filename, utc=False, site=False, var=Fal if series is None: return False - writer = csv.writer(open(filename, 'wb')) - plainWriter = open(filename, 'w') print "filename: " print filename - self.write_data_header(plainWriter, series, utc, site, var, offset, qual, src, qcl) + plainWriter = open(filename, 'w') + self.write_text_header(plainWriter, series, utc, site, var, offset, qual, src, qcl) + plainWriter.close() + writer = csv.writer(open(filename, 'a')) + self.write_data_header(writer, utc, site, var, offset, qual, src, qcl) # for dv in series.data_values: - # self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) + # self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) def export_data(self, series_ids, filename): if series_ids is None: @@ -37,12 +39,132 @@ def export_data(self, series_ids, filename): if file_exists: pass - def write_data_header(self, plainWriter, series, utc, site, var, offset, qual, src, qcl): + def write_data_header(self, writer, utc, site, var, offset, qual, src, qcl): + # Build header list + header = [] + header.append("SeriesId") + header.append("ValueId") + header.append("DataValue") + header.append("ValueAccuracy") + header.append("LocalDateTime") + if utc: + header.append("UTCOffset") + header.append("DateTimeUTC") + header.append("SiteCode") + if site: + header.append("SiteName") + header.append("SiteType") + header.append("Latitude") + header.append("Longitude") + header.append("SRSName") + header.append("VariableCode") + if var: + header.append("VariableName") + header.append("Speciation") + header.append("VariableUnitsName") + header.append("VariableUnitsAbbreviation") + header.append("SampleMedium") + header.append("OffsetValue") + header.append("OffsetTypeID") + if offset: + header.append("OffsetDescription") + header.append("OffsetUnitsName") + header.append("CensorCode") + header.append("QualifierID") + if qual: + header.append("QualifierCode") + header.append("QualifierDescription") + if src: + header.append("Organization") + header.append("SourceDescription") + header.append("Citation") + if qcl: + header.append("QualityControlLevelCode") + header.append("Definition") + header.append("Explanation") + header.append("SampleID") + + writer.writerow(header) + + def write_data_row(self, writer, series, dv, utc, site, var, offset, qual, src, qcl): + data = [] + data.append(series.id) + data.append(dv.id) + data.append(dv.data_value) + data.append(dv.value_accuracy) + data.append(dv.local_date_time) + if utc: + data.append(dv.utc_offset) + data.append(dv.date_time_utc) + data.append(series.site_code) + if site: + data.append(series.site_name) + data.append(series.site.type) + data.append(series.site.latitude) + data.append(series.site.longitude) + data.append(series.site.spatial_ref.srs_name) + data.append(series.variable_code) + if var: + data.append(series.variable_name) + data.append(series.speciation) + data.append(series.variable_units_name) + data.append(series.variable.variable_unit.abbreviation) + data.append(series.sample_medium) + data.append(dv.offset_value) + data.append(dv.offset_type_id) + if offset: + if dv.offset_type is not None: + data.append(dv.offset_type.description) + data.append(dv.offset_type.unit.name) + else: + data.append('') + data.append('') + data.append(dv.censor_code) + data.append(dv.qualifier_id) + if qual: + if dv.qualifier is not None: + data.append(dv.qualifier.code) + data.append(dv.qualifier.description) + else: + data.append('') + data.append('') + if src: + data.append(series.organization) + data.append(series.source_description) + data.append(series.citation) + if qcl: + data.append(series.quality_control_level_code) + data.append(series.quality_control_level.definition) + data.append(series.quality_control_level.explanation) + data.append(dv.sample_id) + + writer.writerow(data) + + def write_text_header(self, plainWriter, series, utc, site, var, offset, qual, src, qcl): self.write_warning_header(plainWriter) self.write_site_information(plainWriter, series, site) self.write_variable_and_method_information(plainWriter, series) + self.write_source_information(plainWriter, series) + self.write_qualifier_information(plainWriter, series) + def write_warning_header(self, plainWriter): + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('# WARNING: The data are released on the condition that neither iUTAH nor any of its \n') + plainWriter.write('# participants may be held liable for any damages resulting from their use. The following \n') + plainWriter.write('# metadata describe the data in this file:\n') + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('#\n') + plainWriter.write('# Quality Control Level Information\n') + plainWriter.write('# -----------------------------------------------\n') + plainWriter.write('# These data have passed QA/QC procedures such as sensor calibration and\n') + plainWriter.write('# visual inspection and removal of obvious errors. These data are approved\n') + plainWriter.write('# by Technicians as the best available version of the data. See published\n') + plainWriter.write('# script for correction steps specific to this data series.\n') + plainWriter.write('#\n') + def write_site_information(self, plainWriter, series, site): plainWriter.write('# Site Information\n') plainWriter.write('# ----------------------------------\n') @@ -65,21 +187,59 @@ def write_variable_and_method_information(self, plainWriter, series): plainWriter.write('# Variable and Method Information\n') plainWriter.write('# ----------------------------------\n') plainWriter.write('# VariableCode: ' + str(series.VariableObj.VariableCode) + '\n') - plainWriter.write('# VariableName: ' + str(series.VariableObj.VariableName) + '\n') + plainWriter.write('# VariableName: ' + str(series.VariableObj.VariableNameCV) + '\n') + plainWriter.write('# ValueType: ' + 'TBD' + '\n') + plainWriter.write('# DataType: ' + 'TBD' + '\n') + plainWriter.write('# GeneralCategory: ' + 'TBD' + '\n') + plainWriter.write('# SampleMedium: ' + 'TBD' + '\n') + plainWriter.write('# VariableUnitsName: ' + str(series.UnitsObj.UnitsName) + '\n') + plainWriter.write('# VariableUnitsType: ' + str(series.UnitsObj.UnitsTypeCV) + '\n') + plainWriter.write('# VariableUnitsAbbreviation: ' + str(series.UnitsObj.UnitsAbbreviation) + '\n') + plainWriter.write('# NoDataValue: ' + str(series.VariableObj.NoDataValue) + '\n') + plainWriter.write('# TimeSupport: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsAbbreviation: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsType: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsName: ' + 'TBD' + '\n') + plainWriter.write('# MethodDescription: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.MethodDescription) + '\n') + plainWriter.write('# MethodLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.MethodLink) + '\n') + plainWriter.write('#\n') + + def write_source_information(self, plainWriter, series): + plainWriter.write('# Source Information\n') + plainWriter.write('# ----------------------------------\n') + if(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj != None): + plainWriter.write('# Organization: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationName) + '\n') + plainWriter.write('# SourceDescription: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationDescription) + + '\n') + plainWriter.write('# SourceLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationLink) + '\n') + plainWriter.write('# ContactName: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj. + AffiliationObj.PersonObj.PersonFirstName) + ' ' + ( + series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj. + AffiliationObj.PersonObj.PersonLastName + ) + '\n') + plainWriter.write('# SourceLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.AffiliationObj.PrimaryPhone) + '\n') + plainWriter.write('# SourceLink: ' + + str( + series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.AffiliationObj.PrimaryEmail) + '\n') + plainWriter.write('# Citation: ' + 'TBD' + '\n') - def write_warning_header(self, plainWriter): - plainWriter.write( - '# ------------------------------------------------------------------------------------------\n') - plainWriter.write('# WARNING: The data are released on the condition that neither iUTAH nor any of its \n') - plainWriter.write('# participants may be held liable for any damages resulting from their use. The following \n') - plainWriter.write('# metadata describe the data in this file:\n') - plainWriter.write( - '# ------------------------------------------------------------------------------------------\n') plainWriter.write('#\n') - plainWriter.write('# Quality Control Level Information\n') - plainWriter.write('# -----------------------------------------------\n') - plainWriter.write('# These data have passed QA/QC procedures such as sensor calibration and\n') - plainWriter.write('# visual inspection and removal of obvious errors. These data are approved\n') - plainWriter.write('# by Technicians as the best available version of the data. See published\n') - plainWriter.write('# script for correction steps specific to this data series.\n') - plainWriter.write('#\n') \ No newline at end of file + + def write_qualifier_information(self, plainWriter, series): + plainWriter.write('# Qualifier Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# Code Description\n') + plainWriter.write('# LI Linear Interpolation\n') + plainWriter.write('# SM Sensor Malfunction\n') + plainWriter.write('# PF Power Failure\n') + plainWriter.write('# S Suspicious Values\n') + plainWriter.write('# MNT Erroneous or missing data due to maintenance\n') + plainWriter.write('#\n') + plainWriter.write('#\n') From daeff4bf6333d1dbee5c3462bae44a7f746f1eef Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Wed, 7 Dec 2016 11:48:04 -0700 Subject: [PATCH 117/158] finished up data export --- odmtools/odmservices/export_data.py | 8 +++++++- odmtools/odmservices/series_service.py | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/odmtools/odmservices/export_data.py b/odmtools/odmservices/export_data.py index 7d64d17..549cade 100644 --- a/odmtools/odmservices/export_data.py +++ b/odmtools/odmservices/export_data.py @@ -23,8 +23,14 @@ def export_series_data(self, series_id, filename, utc=False, site=False, var=Fal plainWriter.close() writer = csv.writer(open(filename, 'a')) self.write_data_header(writer, utc, site, var, offset, qual, src, qcl) - # for dv in series.data_values: + # for dv in self._series_service.get_values(series.ResultID): # self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) + vals = self._series_service.get_values(series.ResultID) + vals.to_csv(filename, ',', + columns = ['valuedatetime', 'valuedatetimeutcoffset', 'datavalue', 'censorcodecv', 'qualifiercodecv'], + header = ['LocalDateTime', 'UTCOffset', series.VariableObj.VariableCode, 'CensorCode', 'QualifierCode'], + mode = 'a', + index = False) def export_data(self, series_ids, filename): if series_ids is None: diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 07318db..845a161 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -971,3 +971,12 @@ def delete_dvs(self, id_list): print message logger.error(message) raise ex + + def get_values_by_series(self, series_id): + setSchema(self._session_factory.engine) + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) + + return q.all() From bea2e0cca9f77cc362591713a2f36c2f3fbf0bcd Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Thu, 8 Dec 2016 11:41:38 -0700 Subject: [PATCH 118/158] flag form shows up on add point when selecting new annotation Also set the default radio button for proc_level in save wizard. --- .../controller/NewFlagValuesController.py | 17 ++++++++++++---- odmtools/controller/logicCellEdit.py | 20 +++++++++++++++++++ odmtools/controller/olvAddPoint.py | 3 +++ odmtools/odmservices/edit_service.py | 5 ++--- odmtools/view/NewFlagValuesView.py | 20 +++++++++---------- odmtools/view/WizardProcessLevelView.py | 2 +- 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py index 20cf3cf..a933af4 100644 --- a/odmtools/controller/NewFlagValuesController.py +++ b/odmtools/controller/NewFlagValuesController.py @@ -1,20 +1,24 @@ import wx from odmtools.view.NewFlagValuesView import NewFlagValuesView +import odmtools.controller.olvAddPoint class NewFlagValuesController(NewFlagValuesView): def __init__(self, parent, series_service, qualifier_choice, record_service): NewFlagValuesView.__init__(self, parent) + self.parent = parent self.series_service = series_service self.qualifer_choice = qualifier_choice self.record_service = record_service self.__new_annotation = "New Annontation" - annotations = self.series_service.get_all_annotations() - self.append_items_to_annotation(annotations) - self.annotation_combo.SetSelection(0) + if self.qualifer_choice: + annotations = self.series_service.get_all_annotations() + self.append_items_to_annotation(annotations) + self.annotation_combo.Append(self.__new_annotation) + self.annotation_combo.SetSelection(0) self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel) self.Bind(wx.EVT_CLOSE, self.on_cancel) @@ -32,7 +36,9 @@ def append_items_to_annotation(self, annotations): def on_cancel(self, event): self.MakeModal(False) self.Destroy() - event.Skip() + + if event: + event.Skip() def on_ok(self, event): selection = self.annotation_combo.GetValue() @@ -46,6 +52,9 @@ def on_ok(self, event): annotation = self.series_service.get_annotation_by_code(code) self.record_service.flag(annotation.AnnotationID) + if isinstance(self.parent, odmtools.controller.olvAddPoint.OLVAddPoint): + self.parent.refresh_annotations() + self.on_cancel(event) if __name__ == '__main__': diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py index 1afb0eb..1ad5267 100644 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -3,6 +3,7 @@ import wx import wx.combo from wx.lib import masked +from odmtools.controller.NewFlagValuesController import NewFlagValuesController __author__ = 'Jacob' @@ -324,8 +325,27 @@ def setComboForTimeAggregationUnitIDCreator(self, olv, rowIndex, subItemIndex): def setComboForAnnotation(self, olv, rowIndex, subItemIndex): customCombo = CustomComboBox(olv, choices=self.annotationChoices, style=wx.CB_READONLY) customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + customCombo.Bind(wx.EVT_COMBOBOX, self.on_annotation_combo_change) return customCombo + def on_annotation_combo_change(self, event): + if not event: + return + + combo = event.GetEventObject() + if combo.GetValue() == NEW: + self.__show_flag_controller() + + event.Skip() + + def __show_flag_controller(self): + add_flag_controller = NewFlagValuesController(self.parent, series_service=self.series_service, + qualifier_choice=None, + record_service=self.recordService) + + add_flag_controller.collapsible_panel.expand_panel() + add_flag_controller.Show() + class DatePicker(wx.DatePickerCtrl): """ diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py index dbdaeab..57f6eaa 100644 --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -107,6 +107,9 @@ def __init__(self, *args, **kwargs): self.oddRowsBackColor = wx.Colour(191, 239, 255) self.cellEditMode = self.CELLEDIT_DOUBLECLICK + def refresh_annotations(self): + self.cellEdit.annotationChoices = self.cellEdit.fetch_annotations() + def buildOlv(self): columns = [ ColumnDefn(title="DataValue", diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 0227f0b..3a9e794 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -566,18 +566,17 @@ def getResult(self, var, meth, proc, action, action_by): if meth: action.MethodID = meth.MethodID - result.FeatureActionObj.ActionObj.MethodID = meth.MethodID + action.MethodObj = meth.MethodObj #if result does not exist if not self.memDB.series_service.resultExists(result): try: #create Action action.ActionID = None - #action.MethodObj = None action.ActionTypeCV = "Derivation" self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) self.memDB.series_service.read._session.expunge(action.MethodObj) - action = self.memDB.series_service.create.create(action) + action = self.memDB.series_service.create.createAction(action) # it times out. find out why print action diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py index 63e42a9..850cdc6 100644 --- a/odmtools/view/NewFlagValuesView.py +++ b/odmtools/view/NewFlagValuesView.py @@ -17,15 +17,15 @@ def __init__(self, parent): content_panel.SetupScrolling() annotation_title = wx.StaticText(content_panel, label="Annotation") - collapsible_panel = CustomCollapsiblePanel(content_panel, title="Panel 1", expand=0, use_combo=True, combo_trigger_item="New Annontation") - self.annotation_combo = collapsible_panel.interactive_item + self.collapsible_panel = CustomCollapsiblePanel(content_panel, title="Panel 1", expand=0, use_combo=True, combo_trigger_item="New Annontation") + self.annotation_combo = self.collapsible_panel.interactive_item - code_title = wx.StaticText(collapsible_panel, label="Code") - self.code_textbox = wx.TextCtrl(collapsible_panel, size=(100, -1)) - text_title = wx.StaticText(collapsible_panel, label="Text") - self.text_textbox = wx.TextCtrl(collapsible_panel) - link_text = wx.StaticText(collapsible_panel, label="Link") - self.link_textbox = wx.TextCtrl(collapsible_panel) + code_title = wx.StaticText(self.collapsible_panel, label="Code") + self.code_textbox = wx.TextCtrl(self.collapsible_panel, size=(100, -1)) + text_title = wx.StaticText(self.collapsible_panel, label="Text") + self.text_textbox = wx.TextCtrl(self.collapsible_panel) + link_text = wx.StaticText(self.collapsible_panel, label="Link") + self.link_textbox = wx.TextCtrl(self.collapsible_panel) sizer = wx.BoxSizer(wx.VERTICAL) content_panel_sizer = wx.BoxSizer(wx.VERTICAL) @@ -36,10 +36,10 @@ def __init__(self, parent): content_panel_sizer.Add(link_text, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) content_panel_sizer.Add(self.link_textbox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) - collapsible_panel.finish_layout() + self.collapsible_panel.finish_layout() sizer.Add(annotation_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) - sizer.Add(collapsible_panel, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + sizer.Add(self.collapsible_panel, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) content_panel.SetSizer(sizer) ########################################## diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py index 1b9c002..39d4509 100644 --- a/odmtools/view/WizardProcessLevelView.py +++ b/odmtools/view/WizardProcessLevelView.py @@ -27,9 +27,9 @@ def __init__(self, parent): font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) header_text.SetFont(font) self.SetupScrolling() + self.existing_process_radio.SetValue(True) self.existing_process_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) - # Add components to sizer table_sizer = wx.BoxSizer(wx.VERTICAL) table_sizer.Add(self.existing_process_table, 0, wx.EXPAND | wx.ALL, 0) From b1afe954f004056ff939d74cfae60531dbd2ff91 Mon Sep 17 00:00:00 2001 From: stephanie Date: Thu, 8 Dec 2016 11:43:15 -0700 Subject: [PATCH 119/158] update result, adjust valuecount assignement --- odmtools/odmservices/edit_service.py | 19 ++++++++++++------- odmtools/odmservices/series_service.py | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 3a9e794..5e7dcee 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -510,13 +510,13 @@ def save_appending(self, result, overwrite=True): vc = result.ValueCount # set in df values["resultid"] = result.ResultID - # update result - self.updateResult(result) + # count = overlap calc count = self.overlapcalc(result, values, overwrite) # set value count = res.vc+valuecount-count - result.ValueCount = result.ValueCount + vc - count - self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) + valuecount = result.ValueCount + vc - count + # update result + self.updateResult(result, valuecount) # insert values self.memDB.series_service.upsert_values(values) # save new annotations @@ -618,7 +618,7 @@ def getResult(self, var, meth, proc, action, action_by): return self.updateResult(result) - def updateResult(self, result): + def updateResult(self, result, valuecount = -10): form = "%Y-%m-%d %H:%M:%S" # get pd values = self.memDB.getDataValuesDF() @@ -627,7 +627,12 @@ def updateResult(self, result): action = result.FeatureActionObj.ActionObj action.BeginDateTime= datetime.datetime.strptime(str(np.min(values['valuedatetime'])), form) action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) - result.ValueCount = len(values) + if valuecount > 0 : + result.ValueCount=valuecount + else: + result.ValueCount = len(values) + + setSchema(self.memDB.series_service._session_factory.engine) self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) self.memDB.series_service.update.updateAction(actionID=action.ActionID, begin=action.BeginDateTime, end=action.EndDateTime) @@ -641,7 +646,7 @@ def overlapcalc(self, result, values, overwrite): dfstart = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) overlap = dbend>= dfstart #number of overlapping values - overlapdf = values[(values["valuedatetime"]>= dfstart) & (values["valudatetime"]<= dbend)] + overlapdf = values[(values["valuedatetime"]<= dfstart) & (values["valuedatetime"]>= dbend)] count =len(overlapdf) #if not overwrite. remove any overlapping values from df if overlap: diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 845a161..aec7c69 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -631,6 +631,7 @@ def get_current_time_and_utcoffset(self): def insert_annotations(self, annotations): annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) + def _get_df_query(self, values): resid = str(values['resultid'][0]) From 43619d27a29be8a42be698cec767407511c40fd8 Mon Sep 17 00:00:00 2001 From: sreeder Date: Thu, 8 Dec 2016 16:40:03 -0700 Subject: [PATCH 120/158] update version and name --- odmtools/controller/olvSeriesSelector.py | 2 +- odmtools/meta/data.py | 8 ++++---- setup/Mac/Welcome.rtf | 2 +- setup/Windows/ODMTools.spec | 2 +- setup/Windows/odmtools_console.iss | 8 ++++---- setup/Windows/odmtools_no_console.iss | 6 +++--- setup/Windows/odmtools_setup.iss | 6 +++--- setup/Windows/odmtools_setup_build.iss | 4 ++-- setup/Windows/test.iss | 6 +++--- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index 8d581dd..e408768 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -50,7 +50,7 @@ def _buildColumns(self, columns): seriesColumns = [ ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, # stringConverter = '%s') - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else '%s') + stringConverter='%Y-%m-%d %H:%M:%S' if ("date" in key.lower()) else '%s') for key in columns] diff --git a/odmtools/meta/data.py b/odmtools/meta/data.py index 8a317c7..45c8988 100644 --- a/odmtools/meta/data.py +++ b/odmtools/meta/data.py @@ -1,12 +1,12 @@ -app_name = "ODMTools" -version = "1.2.8_Beta" +app_name = "ODM2Tools" +version = "2.0.0_Beta" copyright = "Copyright (c) 2013 - 2015, Utah State University. All rights reserved." description = "ODMTools is a python application for managing observational data using the Observations Data Model. " \ "ODMTools allows you to query, visualize, and edit data stored in an Observations Data Model (ODM) database." \ " ODMTools was originally developed as part of the CUAHSI Hydrologic Information System." developers = ["Jeffery S. Horsburgh", "Amber Spackman Jones", - "Stephanie L. Reeder", "Jacob Meline", "James Patton"] + "Stephanie L. Reeder", "Francisco Arrieta", "Mikaila Young", "Jacob Meline", "James Patton"] license = "This material is copyright (c) 2013 - 2015 Utah State University." \ "\nIt is open and licensed under the New Berkeley Software Distribution (BSD) License. Full text of the license follows." \ @@ -18,5 +18,5 @@ "\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. " -website = ("http://uchic.github.io/ODMToolsPython/", "ODMTools home page") +website = ("http://odm2.github.io/ODMToolsPython/", "ODMTools home page") diff --git a/setup/Mac/Welcome.rtf b/setup/Mac/Welcome.rtf index 5df9cc8..0ff97b2 100644 --- a/setup/Mac/Welcome.rtf +++ b/setup/Mac/Welcome.rtf @@ -4,5 +4,5 @@ \margl1440\margr1440\vieww10800\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural -\f0\fs24 \cf0 Please click Continue to install ODMTools for your mac. \ +\f0\fs24 \cf0 Please click Continue to install ODM2Tools for your mac. \ } \ No newline at end of file diff --git a/setup/Windows/ODMTools.spec b/setup/Windows/ODMTools.spec index 464cbf5..b375c2e 100644 --- a/setup/Windows/ODMTools.spec +++ b/setup/Windows/ODMTools.spec @@ -27,7 +27,7 @@ coll = COLLECT(exe, a.datas, strip=False, upx=True, - name='ODMTools') + name='ODM2Tools') diff --git a/setup/Windows/odmtools_console.iss b/setup/Windows/odmtools_console.iss index 659ac95..1190aca 100644 --- a/setup/Windows/odmtools_console.iss +++ b/setup/Windows/odmtools_console.iss @@ -1,13 +1,13 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" +#define MyAppName "ODM2Tools" -#define MyAppVersion "1.2.8_Beta" -#define MyAppExeLongName "ODMTools_1.2.8_Beta_win32_x86_64_console.exe" +#define MyAppVersion "2.0.0_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.0_Beta_win32_x86_64_console.exe" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_no_console.iss b/setup/Windows/odmtools_no_console.iss index d429c56..5f98bcc 100644 --- a/setup/Windows/odmtools_no_console.iss +++ b/setup/Windows/odmtools_no_console.iss @@ -4,12 +4,12 @@ #define MyAppName "ODMTools" -#define MyAppExeLongName "ODMTools_1.2.8_Beta_win32_x86_64.exe" -#define MyAppVersion "1.2.8_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.0_Beta_win32_x86_64.exe" +#define MyAppVersion "2.0.0_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_setup.iss b/setup/Windows/odmtools_setup.iss index 05bf730..a62d90f 100644 --- a/setup/Windows/odmtools_setup.iss +++ b/setup/Windows/odmtools_setup.iss @@ -1,11 +1,11 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" -#define MyAppVersion "1.2.8_Beta" +#define MyAppName "ODM2Tools" +#define MyAppVersion "2.0.0_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_setup_build.iss b/setup/Windows/odmtools_setup_build.iss index 2f2e601..ef66116 100644 --- a/setup/Windows/odmtools_setup_build.iss +++ b/setup/Windows/odmtools_setup_build.iss @@ -3,9 +3,9 @@ ; http://www.jrsoftware.org/isinfo.php ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" +#define MyAppName "ODM2Tools" #define OrgName "UCHIC" -#define MyAppVersion "v1.2.8-beta" +#define MyAppVersion "v2.0.0-beta" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" [Setup] diff --git a/setup/Windows/test.iss b/setup/Windows/test.iss index bd60868..12911f6 100644 --- a/setup/Windows/test.iss +++ b/setup/Windows/test.iss @@ -1,11 +1,11 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" -#define MyAppVersion "1.2.8" +#define MyAppName "ODM2Tools" +#define MyAppVersion "2.0.0" #define MyAppPublisher "My Company, Inc." #define MyAppURL "http://www.example.com/" -#define MyAppExeName "ODMTools_console.exe" +#define MyAppExeName "ODM2Tools_console.exe" [Setup] ; NOTE: The value of AppId uniquely identifies this application. From 120541462d3961154072ab85de8d46d8efc296b1 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 12 Dec 2016 14:30:41 -0700 Subject: [PATCH 121/158] Removing old and unused code --- odmtools/gui/pageMethod.py | 204 ----------------------------------- odmtools/gui/pageQCL.py | 134 ----------------------- odmtools/gui/pageVariable.py | 173 ----------------------------- odmtools/gui/wizSave.py | 181 +------------------------------ 4 files changed, 5 insertions(+), 687 deletions(-) delete mode 100644 odmtools/gui/pageMethod.py delete mode 100644 odmtools/gui/pageQCL.py delete mode 100644 odmtools/gui/pageVariable.py diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py deleted file mode 100644 index e057d18..0000000 --- a/odmtools/gui/pageMethod.py +++ /dev/null @@ -1,204 +0,0 @@ -#Boa:FramePanel:pnlMethods - -import wx -import wx.grid -import wx.richtext -# from odmtools.odmdata import ODM -from odm2api.ODM2.models import Methods - - -[wxID_PNLMETHOD, wxID_PNLMETHODSLISTCTRL1, wxID_PNLMETHODSRBCREATENEW, - wxID_PNLMETHODSRBGENERATE, wxID_PNLMETHODSRBSELECT, - wxID_PNLMETHODSRICHTEXTCTRL1, -] = [wx.NewId() for _init_ctrls in range(6)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - - -class pnlMethod(wx.Panel): # Rename this to page method view - def __init__(self, parent): - wx.Panel.__init__(self, parent) - - # Create components - header_text = wx.StaticText(self, label="Method") - static_line = wx.StaticLine(self, size=(-1, 12)) - - required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) - method_code_text = wx.StaticText(self, label="Method Code") - self.method_code_text_ctrl = wx.TextCtrl(self) - method_name_text = wx.StaticText(self, label="Method Name") - self.method_name_text_ctrl = wx.TextCtrl(self) - method_type_text = wx.StaticText(self, label="Method Type") - self.method_type_combo = wx.ComboBox(self) - - optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) - organization_text = wx.StaticText(self, label="Organization") - self.organization_text_ctrl = wx.TextCtrl(self) - method_link_text = wx.StaticText(self, label="Method Link") - self.method_link_text_ctrl = wx.TextCtrl(self) - description_text = wx.StaticText(self, label="Description") - self.description_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(method_code_text, 0, wx.EXPAND) - row_sizer.Add(self.method_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 24) - required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(method_name_text, 0, wx.EXPAND) - row_sizer.Add(self.method_name_text_ctrl, 1, wx.EXPAND | wx.LEFT, 20) - required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(method_type_text, 0, wx.EXPAND) - row_sizer.Add(self.method_type_combo, 1, wx.EXPAND | wx.LEFT, 26) - required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(organization_text, 0, wx.EXPAND) - row_sizer.Add(self.organization_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) - optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(method_link_text, 0, wx.EXPAND) - row_sizer.Add(self.method_link_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) - optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - row_sizer = wx.BoxSizer(wx.HORIZONTAL) - row_sizer.Add(description_text, 0, wx.EXPAND) - row_sizer.Add(self.description_text_ctrl, 1, wx.EXPAND | wx.LEFT, 34) - optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) - - # Style Components - font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) - header_text.SetFont(font) - - main_sizer = wx.BoxSizer(wx.VERTICAL) - - # Add components to sizer - main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) - main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) - main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) - main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) - self.SetSizer(main_sizer) - -# class pnlMethod(wx.Panel): -# def _init_ctrls(self, prnt): -# # generated method, don't edit -# wx.Panel.__init__(self, id=wxID_PNLMETHOD, name=u'pnlMethod', -# parent=prnt, pos=wx.Point(135, 307), size=wx.Size(439, 357), -# style=wx.TAB_TRAVERSAL) -# self.SetClientSize(wx.Size(423, 319)) -# -# self.rbGenerate = wx.RadioButton(id=wxID_PNLMETHODSRBGENERATE, -# label=u'Automatically generate a Method ', name=u'rbGenerate', -# parent=self, pos=wx.Point(16, 8), size=wx.Size(392, 16), style=0) -# self.rbGenerate.SetValue(True) -# self.rbGenerate.Bind(wx.EVT_RADIOBUTTON, self.OnRbGenerateRadiobutton, -# id=wxID_PNLMETHODSRBGENERATE) -# -# self.rbSelect = wx.RadioButton(id=wxID_PNLMETHODSRBSELECT, -# label=u'Select an existing Method', name=u'rbSelect', parent=self, -# pos=wx.Point(16, 32), size=wx.Size(392, 13), style=0) -# self.rbSelect.SetValue(False) -# self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, -# id=wxID_PNLMETHODSRBSELECT) -# -# self.rbCreateNew = wx.RadioButton(id=wxID_PNLMETHODSRBCREATENEW, -# label=u'Create a new Method ', name=u'rbCreateNew', parent=self, -# pos=wx.Point(16, 208), size=wx.Size(392, 13), style=0) -# self.rbCreateNew.SetValue(False) -# self.rbCreateNew.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateNewRadiobutton, -# id=wxID_PNLMETHODSRBCREATENEW) -# -# self.txtMethodDescrip = wx.richtext.RichTextCtrl(id=wxID_PNLMETHODSRICHTEXTCTRL1, -# parent=self, pos=wx.Point(16, 224), size=wx.Size(392, 84), -# style=wx.richtext.RE_MULTILINE, value=u'Method Description') -# self.txtMethodDescrip.Enable(False) -# self.txtMethodDescrip.Bind(wx.EVT_SET_FOCUS, self.OnTxtMethodDescripSetFocus) -# self.txtMethodDescrip.Bind(wx.EVT_KILL_FOCUS, self.OnTxtMethodDescripKillFocus) -# -# self.lstMethods = wx.ListCtrl(id=wxID_PNLMETHODSLISTCTRL1, -# name='lstMethods', parent=self, pos=wx.Point(16, 48), -# size=wx.Size(392, 152), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) -# self.lstMethods.Bind(wx.EVT_SET_FOCUS, self.OnLstMethodSetFocus) -# -# -# self.lstMethods.InsertColumn(0, 'Description') -# self.lstMethods.InsertColumn(1, 'Link') -# self.lstMethods.InsertColumn(2, 'id') -# self.lstMethods.SetColumnWidth(0, 200) -# self.lstMethods.SetColumnWidth(1, 153) -# self.lstMethods.SetColumnWidth(2,0) -# # self.lstMethods.Enable(False) -# -# -# -# -# def __init__(self, parent, id, pos, size, style, name, ss, method): -# self.series_service = ss -# self.prev_val = method -# self._init_ctrls(parent) -# -# def OnLstMethodSetFocus(self, event): -# self.rbSelect.SetValue(True) -# -# def OnRbGenerateRadiobutton(self, event): -# # self.lstMethods.Enable(False) -# self.txtMethodDescrip.Enable(False) -# -# event.Skip() -# -# def OnRbSelectRadiobutton(self, event): -# self.lstMethods.Enable(True) -# self.txtMethodDescrip.Enable(False) -# -# event.Skip() -# -# def OnRbCreateNewRadiobutton(self, event): -# # self.lstMethods.Enable(False) -# self.txtMethodDescrip.Enable(True) -# -# event.Skip() -# -# def OnTxtMethodDescripSetFocus(self, event): -# if self.txtMethodDescrip.GetValue() =="Method Description": -# self.txtMethodDescrip.SetValue("") -# -# event.Skip() -# -# def OnTxtMethodDescripKillFocus(self, event): -# if self.txtMethodDescrip.GetValue() =="": -# self.txtMethodDescrip.SetValue("Method Description") -# -# event.Skip() -# -# -# def getMethod(self): -# -# m = Method() -# if self.rbGenerate.Value: -# genmethod = "Values derived from ODM Tools Python" -# m= self.series_service.get_method_by_description(genmethod) -# if m is None: -# logger.debug("assigning new method description") -# m = Method() -# m.description = genmethod -# -# elif self.rbSelect.Value: -# index = self.lstMethods.GetFirstSelected() -# desc= self.lstMethods.GetItem(index, 0).GetText() -# -# logger.debug(desc) -# m= self.series_service.get_method_by_description(desc) -# -# -# -# elif self.rbCreateNew.Value: -# logger.debug("assigning new method description") -# m.description = self.txtMethodDescrip.GetValue() -# return m diff --git a/odmtools/gui/pageQCL.py b/odmtools/gui/pageQCL.py deleted file mode 100644 index e406e61..0000000 --- a/odmtools/gui/pageQCL.py +++ /dev/null @@ -1,134 +0,0 @@ -#Boa:FramePanel:pnlQCL - -import wx - - -[wxID_PNLQCL, wxID_PNLQCLLBLCODE, wxID_PNLQCLLBLDEFINITION, - wxID_PNLQCLLBLEXPLANATION, wxID_PNLQCLLSTQCL, wxID_PNLQCLRBCREATE, - wxID_PNLQCLRBSELECT, wxID_PNLQCLTXTCODE, wxID_PNLQCLTXTDEFINITION, - wxID_PNLQCLTXTEXPLANATION, -] = [wx.NewId() for _init_ctrls in range(10)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -class pnlQCL(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLQCL, name=u'pnlQCL', parent=prnt, - pos=wx.Point(589, 303), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbSelect = wx.RadioButton(id=wxID_PNLQCLRBSELECT, - label=u'Select an existing Quality Control Level', - name=u'rbSelect', parent=self, pos=wx.Point(16, 8), - size=wx.Size(392, 13), style=0) - self.rbSelect.SetValue(True) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLQCLRBSELECT) - - self.rbCreate = wx.RadioButton(id=wxID_PNLQCLRBCREATE, - label=u'Create Quality Control Level', name=u'rbCreate', - parent=self, pos=wx.Point(16, 168), size=wx.Size(392, 13), - style=0) - self.rbCreate.SetValue(False) - self.rbCreate.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateRadiobutton, - id=wxID_PNLQCLRBCREATE) - - self.lblCode = wx.StaticText(id=wxID_PNLQCLLBLCODE, label=u'Code:', - name=u'lblCode', parent=self, pos=wx.Point(40, 184), - size=wx.Size(30, 13), style=0) - - self.txtCode = wx.TextCtrl(id=wxID_PNLQCLTXTCODE, name=u'txtCode', - parent=self, pos=wx.Point(88, 184), size=wx.Size(320, 21), - style=0, value=u'') - self.txtCode.Enable(False) - - self.lblDefinition = wx.StaticText(id=wxID_PNLQCLLBLDEFINITION, - label=u'Definition:', name=u'lblDefinition', parent=self, - pos=wx.Point(24, 216), size=wx.Size(50, 13), style=0) - - self.txtDefinition = wx.TextCtrl(id=wxID_PNLQCLTXTDEFINITION, - name=u'txtDefinition', parent=self, pos=wx.Point(88, 216), - size=wx.Size(320, 21), style=0, value=u'') - self.txtDefinition.Enable(False) - - self.lstQCL = wx.ListCtrl(id=wxID_PNLQCLLSTQCL, name=u'lstQCL', - parent=self, pos=wx.Point(16, 24), size=wx.Size(392, 136), - style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstQCL.InsertColumn(0, 'Code') - self.lstQCL.InsertColumn(1, 'Definition') - self.lstQCL.InsertColumn(2, 'Explanation') - self.lstQCL.InsertColumn(3, 'id') - self.lstQCL.SetColumnWidth(0, 50) - self.lstQCL.SetColumnWidth(1, 50) - self.lstQCL.SetColumnWidth(2, 200) - self.lstQCL.SetColumnWidth(3, 0) - - - - self.lstQCL.Bind(wx.EVT_LIST_ITEM_SELECTED, - self.OnListCtrl1ListItemSelected, id=wxID_PNLQCLLSTQCL) - self.lstQCL.Enable(True) - - self.txtExplanation = wx.TextCtrl(id=wxID_PNLQCLTXTEXPLANATION, - name=u'txtExplanation', parent=self, pos=wx.Point(88, 248), - size=wx.Size(320, 64), style=wx.TE_MULTILINE | wx.TE_WORDWRAP, - value=u'') - self.txtExplanation.Enable(False) - - self.lblExplanation = wx.StaticText(id=wxID_PNLQCLLBLEXPLANATION, - label=u'Explanation:', name=u'lblExplanation', parent=self, - pos=wx.Point(16, 248), size=wx.Size(61, 13), style=0) - - def __init__(self, parent, id, pos, size, style, name, ss, qcl): - self.series_service = ss - self.prev_val = qcl - self._init_ctrls(parent) - - def OnRbSelectRadiobutton(self, event): - self.txtExplanation.Enable(False) - self.txtDefinition.Enable(False) - self.txtCode.Enable(False) - self.lstQCL.Enable(True) - event.Skip() - - def OnRbCreateRadiobutton(self, event): - self.txtExplanation.Enable(True) - self.txtDefinition.Enable(True) - self.txtCode.Enable(True) - self.lstQCL.Enable(False) - event.Skip() - - def OnListCtrl1ListItemSelected(self, event): - event.Skip() - - def GetLstSelection(self): - return self.lstQCL.GetFirstSelected() - - def getQCL(self): - q = None - if self.rbCreate.Value: - q= self.series_service.create_qcl(self.txtCode.Value, self.txtDefinition.Value, self.txtExplanation.Value) - q.code = self.txtCode.Value - q.definition= self.txtDefinition.Value - q.explanation = self.txtExplanation.Value - - elif self.rbSelect.Value: - index = self.GetLstSelection() - logger.debug("lstQCL: %s" %(self.lstQCL)) - code= self.lstQCL.GetItem(index, 0).GetText() - logger.debug(code) - q= self.series_service.get_processing_level_by_code(code) - -## q.id = self.lstQCL.GetItem(index,3).GetText() -## q.code = self.lstQCL.GetItem(index, 0).GetText() -## q.definition= self.lstQCL.GetItem(index, 1).GetText() -## q.explanation=self.lstQCL.GetItem(index, 2).GetText() - - return q - diff --git a/odmtools/gui/pageVariable.py b/odmtools/gui/pageVariable.py deleted file mode 100644 index 582ecad..0000000 --- a/odmtools/gui/pageVariable.py +++ /dev/null @@ -1,173 +0,0 @@ -#Boa:FramePanel:pnlVariable - -import wx -from odmtools.controller.frmCreateVariable import frmCreateVariable -# from odmtools.odmdata import Variable - -[wxID_PNLVARIABLE, wxID_PNLVARIABLELSTVARIABLE, wxID_PNLVARIABLERBCREATE, - wxID_PNLVARIABLERBCURRENT, wxID_PNLVARIABLERBSELECT,wxID_PNLVARIABLETXTNEWVAR, -] = [wx.NewId() for _init_ctrls in range(6)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -class pnlVariable(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLVARIABLE, name=u'pnlVariable', - parent=prnt, pos=wx.Point(1034, 305), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbCurrent = wx.RadioButton(id=wxID_PNLVARIABLERBCURRENT, - label=u'Use Current Variable', name=u'rbCurrent', parent=self, - pos=wx.Point(16, 16), size=wx.Size(384, 13), style=0) - self.rbCurrent.SetValue(True) - self.rbCurrent.Bind(wx.EVT_RADIOBUTTON, self.OnRbCurrentRadiobutton, - id=wxID_PNLVARIABLERBCURRENT) - - self.rbSelect = wx.RadioButton(id=wxID_PNLVARIABLERBSELECT, - label=u'Select an existing Variable', name=u'rbSelect', - parent=self, pos=wx.Point(16, 56), size=wx.Size(384, 16), - style=0) - self.rbSelect.SetValue(False) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLVARIABLERBSELECT) - - self.rbCreate = wx.RadioButton(id=wxID_PNLVARIABLERBCREATE, - label=u'Create New Variable', name=u'rbCreate', parent=self, - pos=wx.Point(16, 256), size=wx.Size(368, 13), style=0) - self.rbCreate.SetValue(False) - self.rbCreate.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateRadiobutton, - id=wxID_PNLVARIABLERBCREATE) - - - self.lstVariable = wx.ListCtrl(id=wxID_PNLVARIABLELSTVARIABLE, - name=u'lstVariable', parent=self, pos=wx.Point(16, 80), - size=wx.Size(392, 160), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstVariable.InsertColumn(0, 'Code') - self.lstVariable.InsertColumn(1, 'Name') - self.lstVariable.InsertColumn(2, 'Speciation') - self.lstVariable.InsertColumn(3, 'Units') - self.lstVariable.InsertColumn(4, 'Sample Medium') - self.lstVariable.InsertColumn(5, 'Value Type') - self.lstVariable.InsertColumn(6, 'IsRegular') - self.lstVariable.InsertColumn(7, 'Time Support') - self.lstVariable.InsertColumn(8, 'Time Units') - self.lstVariable.InsertColumn(9, 'DataType') - self.lstVariable.InsertColumn(10, 'General Category') - self.lstVariable.InsertColumn(11, 'NoDataValue') - self.lstVariable.InsertColumn(12, 'id') - self.lstVariable.SetColumnWidth(0, 50) - self.lstVariable.SetColumnWidth(1, 100) - self.lstVariable.SetColumnWidth(12,0) - - - - self.lstVariable.Bind(wx.EVT_LIST_ITEM_SELECTED, - self.OnListCtrl1ListItemSelected, id=wxID_PNLVARIABLELSTVARIABLE) - - self.lstVariable.Enable(False) - ''' - self.txtNewVar = wx.TextCtrl(id=wxID_PNLVARIABLETXTNEWVAR, - name=u'txtNewVar', parent=self, pos=wx.Point(16, - 276), size=wx.Size(392, 21), style=0, value=u'') - ''' - - self.txtNewVar = wx.ListCtrl(id=wxID_PNLVARIABLELSTVARIABLE, - name=u'txtNewVar', parent=self, pos=wx.Point(16, 276), - size=wx.Size(392, 70), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.txtNewVar.InsertColumn(0, 'Code') - self.txtNewVar.InsertColumn(1, 'Name') - self.txtNewVar.InsertColumn(2, 'Speciation') - self.txtNewVar.InsertColumn(3, 'Units') - self.txtNewVar.InsertColumn(4, 'Sample Medium') - self.txtNewVar.InsertColumn(5, 'Value Type') - self.txtNewVar.InsertColumn(6, 'IsRegular') - self.txtNewVar.InsertColumn(7, 'Time Support') - self.txtNewVar.InsertColumn(8, 'Time Units') - self.txtNewVar.InsertColumn(9, 'DataType') - self.txtNewVar.InsertColumn(10, 'General Category') - self.txtNewVar.InsertColumn(11, 'NoDataValue') - self.txtNewVar.SetColumnWidth(0, 50) - self.txtNewVar.SetColumnWidth(1, 100) - self.txtNewVar.Enable(False) - - def __init__(self, parent, id, pos, size, style, name, sm, var): - self.prev_val= var - self.service_man = sm - self.series_service = sm.get_series_service() - self._init_ctrls(parent) - - def OnRbCurrentRadiobutton(self, event): - self.lstVariable.Enable(False) - self.txtNewVar.Enable(False) - - event.Skip() - - def OnRbSelectRadiobutton(self, event): - self.lstVariable.Enable(True) - self.txtNewVar.Enable(False) - - event.Skip() - - def OnRbCreateRadiobutton(self, event): - self.lstVariable.Enable(False) - - - create_Var = frmCreateVariable(self, self.service_man, self.prev_val) - returnVal = create_Var.ShowModal() - self.createdVar= create_Var.getVariable() - create_Var.Destroy() - - - # TODO if cancelled return to previous radio button - # else enable text box and enter the text info. - # get Variable object - if returnVal == wx.ID_CANCEL: - self.rbCurrent.SetValue(True) - else: - self.show_new_var(self.createdVar) - event.Skip() - - def OnListCtrl1ListItemSelected(self, event): - event.Skip() - - def getVariable(self): - - v = None - if self.rbCurrent.Value: - v= self.prev_val - elif self.rbSelect.Value: - index = self.lstVariable.GetFirstSelected() - code= self.lstVariable.GetItem(index, 0).GetText() - logger.debug(code) - v= self.series_service.get_variable_by_code(code) - elif self.rbCreate.Value: - v = self.createdVar - return v - - - def show_new_var(self, var): - - self.txtNewVar.InsertStringItem(0, str(var.code)) - self.txtNewVar.SetStringItem(0, 1, str(var.name)) - self.txtNewVar.SetStringItem(0, 2, str(var.speciation)) - self.txtNewVar.SetStringItem(0, 3, str(var.variable_unit.name)) - self.txtNewVar.SetStringItem(0, 4, str(var.sample_medium)) - self.txtNewVar.SetStringItem(0, 5, str(var.value_type)) - self.txtNewVar.SetStringItem(0, 6, str(var.is_regular)) - self.txtNewVar.SetStringItem(0, 7, str(var.time_support)) - self.txtNewVar.SetStringItem(0, 8, str(var.time_unit.name)) - self.txtNewVar.SetStringItem(0, 9, str(var.data_type)) - self.txtNewVar.SetStringItem(0, 10, str(var.general_category)) - self.txtNewVar.SetStringItem(0, 11, str(var.no_data_value)) - #self.txtNewVar.SetStringItem(num_items, 12, str(var.id)) - - - self.txtNewVar.Focus(0) - self.txtNewVar.Select(0) - self.txtNewVar.Enable(True) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 63995e0..0576f68 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -1,15 +1,12 @@ -# Boa:Wizard:wizSave - import wx import wx.wizard as wiz - -# import * from WizardPanels from odmtools.controller import pageIntro, pageExisting -import pageMethod -import pageQCL -import pageVariable import pageSummary from odm2api.ODM2.models import * +from odmtools.controller.WizardMethodController import WizardMethodController +from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController +from odmtools.controller.WizardVariableController import WizardVariableController +from odmtools.controller.WizardActionController import WizardActionController [wxID_PNLINTRO, wxID_PNLVARIABLE, wxID_PNLMETHOD, wxID_PNLQCL, wxID_PNLSUMMARY, wxID_WIZSAVE, wxID_PNLEXISTING, @@ -18,149 +15,7 @@ from wx.lib.pubsub import pub as Publisher from odmtools.common.logger import LoggerTool import logging - -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - - -######################################################################## -class QCLPage(wiz.WizardPageSimple): - # CLASS IS DEPECRATED - # REPLACED WITH WizardProcessLevelController.py - def __init__(self, parent, title, series_service, qcl): - """Constructor""" - wiz.WizardPageSimple.__init__(self, parent) - - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.qcl = qcl - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageQCL.pnlQCL(self, id=wxID_PNLQCL, name=u'pnlQCL', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, ss=series_service, qcl=qcl) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series): - qcl = series.get_all_processing_levels() - index = 0 - for q, i in zip(qcl, range(len(qcl))): - num_items = self.panel.lstQCL.GetItemCount() - self.panel.lstQCL.InsertStringItem(num_items, str(q.code)) - self.panel.lstQCL.SetStringItem(num_items, 1, str(q.definition)) - self.panel.lstQCL.SetStringItem(num_items, 2, str(q.explanation)) - self.panel.lstQCL.SetStringItem(num_items, 3, str(q.id)) - if q.code == self.qcl.code: - index = i - self.panel.lstQCL.Focus(index) - self.panel.lstQCL.Select(index) - - -######################################################################## -class VariablePage(wiz.WizardPageSimple): - # CLASS IS DEPECRATED - # REPLACED WITH WizardVariableController.py - def __init__(self, parent, title, service_manager, var): - """Constructor""" - wiz.WizardPageSimple.__init__(self, parent) - - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.variable = var - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageVariable.pnlVariable(self, id=wxID_PNLVARIABLE, name=u'pnlVariable', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, sm=service_manager, var=var) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series_service): - vars = series_service.get_all_variables() - index = 0 - for v, i in zip(vars, range(len(vars))): - num_items = self.panel.lstVariable.GetItemCount() - self.panel.lstVariable.InsertStringItem(num_items, str(v.code)) - self.panel.lstVariable.SetStringItem(num_items, 1, str(v.name)) - self.panel.lstVariable.SetStringItem(num_items, 2, str(v.speciation)) - self.panel.lstVariable.SetStringItem(num_items, 3, str(v.variable_unit.name)) - self.panel.lstVariable.SetStringItem(num_items, 4, str(v.sample_medium)) - self.panel.lstVariable.SetStringItem(num_items, 5, str(v.value_type)) - self.panel.lstVariable.SetStringItem(num_items, 6, str(v.is_regular)) - self.panel.lstVariable.SetStringItem(num_items, 7, str(v.time_support)) - self.panel.lstVariable.SetStringItem(num_items, 8, str(v.time_unit.name)) - self.panel.lstVariable.SetStringItem(num_items, 9, str(v.data_type)) - self.panel.lstVariable.SetStringItem(num_items, 10, str(v.general_category)) - self.panel.lstVariable.SetStringItem(num_items, 11, str(v.no_data_value)) - self.panel.lstVariable.SetStringItem(num_items, 12, str(v.id)) - - if v.code == self.variable.code: - index = i - self.panel.lstVariable.Focus(index) - self.panel.lstVariable.Select(index) - - -######################################################################## -class MethodPage(wiz.WizardPageSimple): - # THIS CLASS IS DEPECRATED - # REPLACED WITH WizardMethodController.py - def __init__(self, parent): - # pageMethod.pnlMethod.__init__(self, parent) - wiz.WizardPageSimple.__init__(self, parent) - - main_sizer = wx.BoxSizer(wx.VERTICAL) - self.page_method_view = pageMethod.pnlMethod(self) - main_sizer.Add(self.page_method_view, 1, wx.EXPAND | wx.ALL, 0) - self.SetSizer(main_sizer) - -# class MethodPage(wiz.WizardPageSimple): -# def __init__(self, parent, title, series_service, method): -# """Constructor""" -# wiz.WizardPageSimple.__init__(self, parent) -# -# sizer = wx.BoxSizer(wx.VERTICAL) -# self.sizer = sizer -# self.SetSizer(sizer) -# self.method = method -# -# title = wx.StaticText(self, -1, title) -# title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) -# sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) -# sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) -# self.panel = pageMethod.pnlMethod(self, id=wxID_PNLMETHOD, name=u'pnlMethod', -# pos=wx.Point(536, 285), size=wx.Size(439, 357), -# style=wx.TAB_TRAVERSAL, ss=series_service, method=method) -# self.sizer.Add(self.panel, 1, wx.EXPAND, 5) -# -# self._init_data(self.panel.series_service) -# -# def _init_data(self, series_service): -# meth = series_service.get_all_methods() -# index = 0 -# for m, i in zip(meth, range(len(meth))): -# num_items = self.panel.lstMethods.GetItemCount() -# self.panel.lstMethods.InsertStringItem(num_items, str(m.description)) -# self.panel.lstMethods.SetStringItem(num_items, 1, str(m.link)) -# self.panel.lstMethods.SetStringItem(num_items, 2, str(m.id)) -# -# if m.description == self.method.description: -# index = i -# -# self.panel.lstMethods.Focus(index) -# self.panel.lstMethods.Select(index) - +logger = logging.getLogger('main') ######################################################################## class SummaryPage(wiz.WizardPageSimple): @@ -204,10 +59,6 @@ def fill_summary(self): ######################################################################## -from odmtools.controller.WizardMethodController import WizardMethodController -from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController -from odmtools.controller.WizardVariableController import WizardVariableController -from odmtools.controller.WizardActionController import WizardActionController class wizSave(wx.wizard.Wizard): @@ -225,28 +76,6 @@ def _init_ctrls(self, prnt): self.Bind(wx.wizard.EVT_WIZARD_FINISHED, self.on_wizard_finished) def get_metadata(self): - # method = self.currSeries.FeatureActionObj.ActionObj.MethodObj - # processing_level = self.currSeriefs.quality_control_level - # variable = self.currSeries.variable - # action = - - # if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): - # logger.debug("SaveAs") - # method = self.pgMethod.get_method() - # processing_level = self.pgQCL.get_processing_level() - # variable = self.pgVariable.get_variable() - # action = self.action_page.get_action() - # elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): - # logger.debug("Save") - # elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): - # logger.debug("Existing") - # method, processing_level, variable = self.pgExisting.getSeries() - # site = self.currSeries.FeatureActionObj.SamplingFeatureObj - # # source = self.currSeries.source - # logger.debug("site: %s, variable: %s, method: %s, source: %s, processing_level: %s" % ( - # str(site), str(variable), str(method), str(action), str(processing_level))) - # return site, variable, method, action, processing_level - # method = self.__method_from_series processing_level = self.__processing_level_from_series variable = self.__variable_from_series From 9aa05f8867248d228f794e371800e660dec31d89 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 13 Dec 2016 16:45:53 -0700 Subject: [PATCH 122/158] fix improper location of ResultID deletion --- odmtools/odmservices/edit_service.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 5e7dcee..e56717a 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -551,8 +551,7 @@ def getResult(self, var, meth, proc, action, action_by): values = self.memDB.getDataValuesDF() # copy old result = self.memDB.series_service.get_series(str(values["resultid"][0])) - result.ResultID = None - result.ResultUUID = None + # change var, meth proc, in df #intend ts, agg sta @@ -571,6 +570,7 @@ def getResult(self, var, meth, proc, action, action_by): #if result does not exist if not self.memDB.series_service.resultExists(result): try: + #create Action action.ActionID = None action.ActionTypeCV = "Derivation" @@ -601,6 +601,8 @@ def getResult(self, var, meth, proc, action, action_by): # create TimeSeriesResult - this should also contain all of the stuff for the Result time, offset = self.get_current_time_and_utcoffset() + result.ResultID = None + result.ResultUUID = None result.ValueCount = 0 result.FeatureActionID = feature_action.FeatureActionID result.ResultDateTime = time From 557560f9e9c67514f4570df18a348f7184bc4585 Mon Sep 17 00:00:00 2001 From: sreeder Date: Thu, 15 Dec 2016 18:13:32 -0700 Subject: [PATCH 123/158] update Naming --- ODMTools.py | 4 ++-- odmtools/gui/frmODMTools.py | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ODMTools.py b/ODMTools.py index 14fc8ee..d6fb62a 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -24,7 +24,7 @@ tool = LoggerTool() # logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.INFO) -logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.DEBUG) +logger = tool.setupLogger('main', 'odm2tools.log', 'a', logging.DEBUG) wx.Log.SetLogLevel(0) @@ -64,7 +64,7 @@ def OnInit(self): if __name__ == '__main__': - logger.info("Welcome to ODMTools Python. Please wait as system loads") + logger.info("Welcome to ODM2Tools Python. Please wait as system loads") # https://docs.python.org/2/library/multiprocessing.html#miscellaneous # Add support for when a program which uses multiprocessing has been frozen to produce a Windows executable. diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index a54b180..602c53f 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -86,7 +86,7 @@ def _obtainScreenResolution(self): ''' if minimumAllowedSize >= wx.GetDisplaySize(): - logger.fatal("ODMTools cannot be displayed in this resolution: %s \n\tPlease use a larger resolution" + logger.fatal("ODM2Tools cannot be displayed in this resolution: %s \n\tPlease use a larger resolution" % wx.GetDisplaySize()) print "minimumAllowedsize: ", minimumAllowedSize, "display: ", wx.GetDisplaySize() sys.exit(0) @@ -104,7 +104,7 @@ def _obtainScreenResolution(self): elif screenWidth < defaultWidth: newSize = wx.Size(defaultHeight, screenWidth / 1.5) - logger.debug("ODMTools Window Size: %s" % newSize) + logger.debug("ODM2Tools Window Size: %s" % newSize) return newSize #############Entire Form Sizers########## @@ -137,7 +137,7 @@ def _init_database(self, quit_if_cancel=True): db_config = frmDBConfig.frmDBConfig(None, self.service_manager, False) value = db_config.ShowModal() if value == wx.ID_CANCEL and quit_if_cancel: - logger.fatal("ODMTools is now closing because there is no database connection.") + logger.fatal("ODM2Tools is now closing because there is no database connection.") sys.exit(0) elif not quit_if_cancel: return series_service @@ -234,7 +234,7 @@ def _init_ctrls(self, series_service): self.menu_bar = wx.MenuBar() self.help_menu = wx.Menu() - self.help_menu.Append(wx.ID_ABOUT, "&About ODMTools") + self.help_menu.Append(wx.ID_ABOUT, "&About ODM2Tools") self.menu_bar.Append(self.help_menu, "&Help") self.SetMenuBar(self.menu_bar) @@ -381,7 +381,7 @@ def onSetScriptTitle(self, title): def addEdit(self, event): - with wx.BusyInfo("Please wait for a moment while ODMTools fetches the data and stores it in our database", parent=self): + with wx.BusyInfo("Please wait for a moment while ODM2Tools fetches the data and stores it in our database", parent=self): self.scriptcreate = True isSelected, seriesID = self.pnlSelector.onReadyToEdit() logger.info("Beginning editing seriesID: %s"%str(seriesID)) @@ -481,12 +481,12 @@ def loadDockingSettings(self): # test if there is a perspective to load try: # TODO Fix resource_path to appdirs - os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config') - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'r') + os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config') + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'r') except: # Create the file if it doesn't exist - open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'w').close() - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'r') + open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'w').close() + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'r') self._mgr.LoadPerspective(f.read(), True) @@ -507,7 +507,7 @@ def onClose(self, event): # deinitialize the bulkInsertCtrl manager self.pnlPlot.Close() try: - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'w') + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'w') f.write(self._mgr.SavePerspective()) except: print "error saving docking data" @@ -516,7 +516,7 @@ def onClose(self, event): # Shut down processes running in background if self.taskserver.numprocesses > 0 and self.taskserver.anyAlive: - busy = wx.BusyInfo("Closing ODMTools ...", parent=self) + busy = wx.BusyInfo("Closing ODM2Tools ...", parent=self) # Terminate the processes self.taskserver.processTerminate() @@ -539,7 +539,7 @@ def onClose(self, event): elif isinstance(item, wx.Dialog): item.Destroy() item.Close() - logger.info("Closing ODMTools\n") + logger.info("Closing ODM2Tools\n") self.Destroy() wx.GetApp().ExitMainLoop() From 23750c6394d19d5b71949b2ba9589c0192e655fa Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Fri, 16 Dec 2016 10:18:12 -0700 Subject: [PATCH 124/158] Data Table shows annotation code Need to optimize and clean --- odmtools/controller/frmDataTable.py | 9 +-- odmtools/controller/olvDataTable.py | 47 ++++++++++++-- odmtools/odmdata/memory_database.py | 34 +++++++++- odmtools/odmservices/series_service.py | 90 ++++++++++++-------------- 4 files changed, 121 insertions(+), 59 deletions(-) diff --git a/odmtools/controller/frmDataTable.py b/odmtools/controller/frmDataTable.py index ce6f008..be6ccbe 100644 --- a/odmtools/controller/frmDataTable.py +++ b/odmtools/controller/frmDataTable.py @@ -13,17 +13,14 @@ def __init__(self, parent, **kwargs): self.memDB = None DataTable.__init__(self, parent, **kwargs) + def init(self, memDB): + self.memDB = memDB + self.olvDataTable.init(self.memDB) - def init_publishers(self): Publisher.subscribe(self.onChangeSelection, "changeTableSelection") Publisher.subscribe(self.onRefresh, "refreshTable") Publisher.subscribe(self.olvDataTable.onDeselectAll, "deselectAllDataTable") - def init(self, memDB): - self.memDB = memDB - self.olvDataTable.init(self.memDB) - self.init_publishers() - def onItemSelected(self, event): pass diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 112f4d7..8007aba 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -20,6 +20,8 @@ def __init__(self, parent, **kwargs): self.sortedColumnIndex = -1 self.currentItem = None self.dataframe = None + self.annotations = None + self.data = None def init(self, memDB): self.memDB = memDB @@ -28,19 +30,54 @@ def init(self, memDB): self.oddRowsBackColor = wx.Colour(191, 217, 217) self.dataframe = self.memDB.getDataValuesDF() - sort_by_index = list(self.dataframe.columns).index("valuedatetime") - self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) - self.dataObjects = self.dataframe.values.tolist() + self.annotations = self.memDB.get_annotations() + # sort_by_index = list(self.dataframe.columns).index("valuedatetime") + # columns = self.memDB.get_columns_with_annotations() + # self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) + # self.dataObjects = self.dataframe.values.tolist() + self.dataObjects = self.__merge_dataframe_with_annotations() + col = self.memDB.get_columns_with_annotations() + + # columns = \ + # [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, + # stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') + # for x, i in self.memDB.getEditColumns()] columns = \ [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') - for x, i in self.memDB.getEditColumns()] + stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') + for x, i in col] + self.SetColumns(columns) self.SetObjectGetter(self.ObjectGetter) self.SetItemCount(len(self.dataframe)) + def __merge_dataframe_with_annotations(self): + sort_by_index = self.dataframe.columns.tolist().index("valuedatetime") + self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) + + data_list = self.dataframe.values.tolist() + anno_list = self.annotations.values.tolist() + data = data_list + + anno = {} + for i in range(0, len(anno_list)): + value_id = anno_list[i][1] + annotation_code = anno_list[i][-1] + if value_id in anno: + anno[value_id].append(annotation_code) + else: + anno[value_id] = [annotation_code] + + for key, value in anno.iteritems(): + for i in range(0, len(data_list)): + if key in data_list[i]: + data_list[i].append(value) + break + + return data + def EnableSorting(self): self.Bind(wx.EVT_LIST_COL_CLICK, self.onColSelected) if not self.smallImageList: diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 813fbe9..1bdc792 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -25,6 +25,7 @@ def __init__(self, taskserver=None): self.df = None # Series_Service handles remote database self.series_service = None + self.results_annotations = None # Memory_service handles in memory database sm = ServiceManager() @@ -77,6 +78,16 @@ def getDataValuesDF(self): logging.debug("done updating memory dataframe") return self.df + def get_annotations(self, query_db_again=False): + # self.mem_service._session.commit() + # setSchema(self.mem_service._session_factory.engine) + if self.results_annotations is None or query_db_again: + result_id = self.df.resultid[0] + annotation = self.series_service.get_annotations_by_result(resultid=result_id) + self.results_annotations = annotation + + return self.results_annotations + def getDataValues(self): # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore self.mem_service._session.commit() @@ -94,6 +105,27 @@ def getEditColumns(self): return [(x, i) for (i, x) in enumerate(columns)] # return [(x, i) for (i, x) in enumerate(self.df.columns)] + def get_columns_with_annotations(self): + """ + If results_annotations has not been set then + :return: + """ + + if self.results_annotations is None or self.df is None: + print "self.df and self.results_annotations must be a pandas dataframe. Currently they are None" + return [] + + columns = [] + columns.extend(self.df.columns.tolist()) + + annotation_columns = self.results_annotations.columns.tolist() + index = annotation_columns.index("annotationcode") + annotation_code_column = annotation_columns[index] + + columns.append(annotation_code_column) + + return [(x, i) for (i, x) in enumerate(columns)] + def getDataValuesforGraph(self, seriesID, noDataValue, startDate=None, endDate=None): return self.series_service.get_plot_values(seriesID, noDataValue, startDate, endDate) @@ -250,7 +282,7 @@ def initEditValues(self, seriesID): logger.debug("Load series from db") self.series = self.series_service.get_series(seriesID) - self.df = self.series_service.get_values(series_id= seriesID) + self.df = self.series_service.get_values(series_id=seriesID) self.editLoaded = True diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index aec7c69..4a76710 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -116,26 +116,6 @@ def get_variables_by_site_code(self, site_code): q = self._session.query(Variables).filter(Variables.VariableID.in_(var_ids)) return q.all() - # Data Value Methods - def get_values(self, series_id=None): - ''' - - :param series_id: Series id - :return: pandas dataframe - ''' - - setSchema(self._session_factory.engine) - q = self.read._session.query(TimeSeriesResultValues) - if series_id: - q = q.filter_by(ResultID=series_id) - q = q.order_by(TimeSeriesResultValues.ValueDateTime) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data.set_index(data['valuedatetime'], inplace=True) - return data - # Series Catalog methods def get_series_by_site(self , site_id): # try: @@ -359,42 +339,55 @@ def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass -# + #Data Value Methods def get_values(self, series_id=None): - ''' - :param series_id: Series id + """ + :param series_id: :return: pandas dataframe - ''' - #series= self.get_series_by_id(series_id) - # if series: - # q = self._edit_session.query(DataValue).filter_by( - # site_id=series.site_id, - # variable_id=series.variable_id, - # method_id=series.method_id, - # source_id=series.source_id, - # quality_control_level_id=series.quality_control_level_id) - # - # query=q.statement.compile(dialect=self._session_factory.engine.dialect) - # data= pd.read_sql_query(sql= query, - # con = self._session_factory.engine, - # params = query.params ) - # #return data.set_index(data['LocalDateTime']) - # return data - # else: - # return None + """ + # see get_annotations_by_result around line 850 q = self.read._session.query(TimeSeriesResultValues) if series_id: - q=q.filter_by(ResultID=series_id) - q= q.order_by(TimeSeriesResultValues.ValueDateTime) + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, params=query.params) data.set_index(data['valuedatetime'], inplace=True) + + # if series_id: + # anno = self.get_annotations_by_result(series_id) + # q = pd.merge(data, anno, how="left", on='valueid', indicator=False) + # data.applymap(self.merge_annotation_with_timeseries_result) + + # if len(anno): + # # data.valueid.apply(self.merge_annotation_with_timeseries_result) + # # data.applymap(self.merge_annotation_with_timeseries_result) + # self.merge_annotation_with_timeseries_result(data, anno) + + + return data + # df.drop_duplicates(dup_cols, keep='last', inplace=True) + # newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + # newdf = newdf[newdf['_merge'] == 'left_only'] + # newdf.drop(['_merge'], axis=1, inplace=True) + # return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + + # def merge_annotation_with_timeseries_result(self, x): + # print x + # + # # return x + # return 123 + def merge_annotation_with_timeseries_result(self, data, anno): + + print 123 + return + def get_all_values_df(self): """ @@ -877,17 +870,20 @@ def get_all_annotations(self): return self.read.getAnnotations(type=None) def get_annotations_by_result(self, resultid): - setSchema(self._session_factory.engine) + # setSchema(self._session_factory.engine) # ids = [x[0] for x in self.read._session.query(TimeSeriesResultValues.ValueID)\ # .filter(TimeSeriesResultValues.ResultID == resultid).all()] # q = self.read._session.query(TimeSeriesResultValueAnnotations)\ # .filter(TimeSeriesResultValueAnnotations.ValueID.in_(ids)).all() - q =self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, - TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime)\ + resultid = int(resultid) + + q = self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, + TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime, Annotations.AnnotationCode)\ .filter(TimeSeriesResultValues.ResultID == resultid)\ - .filter(TimeSeriesResultValueAnnotations.ValueID == TimeSeriesResultValues.ValueID) + .filter(TimeSeriesResultValueAnnotations.ValueID == TimeSeriesResultValues.ValueID)\ + .filter(Annotations.AnnotationID==TimeSeriesResultValueAnnotations.AnnotationID) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, From 448d651b4ef713a30efe3a8227943894d8ec25e5 Mon Sep 17 00:00:00 2001 From: sreeder Date: Fri, 16 Dec 2016 10:48:57 -0700 Subject: [PATCH 125/158] add renaming --- ODMTools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ODMTools.py b/ODMTools.py index d6fb62a..0c6837e 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -45,7 +45,7 @@ def OnInit(self): """ Initialize an App with a Frame """ - title = u'ODMTools' + title = u'ODM2Tools' kwargs = {} kwargs['parent'] = None From ef1f364d10e5546bfc7e3244cc433d9be35b6a95 Mon Sep 17 00:00:00 2001 From: sreeder Date: Fri, 16 Dec 2016 10:49:12 -0700 Subject: [PATCH 126/158] rename --- setup/Windows/odmtools_setup_build.iss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/Windows/odmtools_setup_build.iss b/setup/Windows/odmtools_setup_build.iss index ef66116..45726fc 100644 --- a/setup/Windows/odmtools_setup_build.iss +++ b/setup/Windows/odmtools_setup_build.iss @@ -23,7 +23,7 @@ AppUpdatesURL={#MyAppURL} DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} OutputBaseFilename={#MyAppName}_{#MyAppVersion}_Installer -SetupIconFile=D:\DEV\Releases\ODMTools\odmtools_beta_source_code\odmtools\common\icons\ODMTools.ico +SetupIconFile=D:\DEV\Releases\ODM2Tools\odmtools_beta_source_code\odmtools\common\icons\ODMTools.ico Compression=lzma SolidCompression=yes @@ -34,7 +34,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] -Source: "D:\DEV\Releases\ODMTools\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "D:\DEV\Releases\ODM2Tools\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] From b3f5af0bf588fffc352b5d4f3a0a973c290be1e4 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Fri, 16 Dec 2016 11:46:00 -0700 Subject: [PATCH 127/158] Fixed bugs when sorting columns and in series service In series_service.py add try except to some methods to prevent errors. Next, when clicking on a column the data was not being sorted correctly because of the previous changes. Last, optimized the merge_annotation method --- odmtools/controller/olvDataTable.py | 82 +++++++++++++------------- odmtools/odmservices/series_service.py | 34 +++++------ 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 8007aba..e4ff41d 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -21,7 +21,7 @@ def __init__(self, parent, **kwargs): self.currentItem = None self.dataframe = None self.annotations = None - self.data = None + self.annotations_grouped = {} def init(self, memDB): self.memDB = memDB @@ -31,17 +31,15 @@ def init(self, memDB): self.dataframe = self.memDB.getDataValuesDF() self.annotations = self.memDB.get_annotations() - # sort_by_index = list(self.dataframe.columns).index("valuedatetime") - # columns = self.memDB.get_columns_with_annotations() - # self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) - # self.dataObjects = self.dataframe.values.tolist() + + sort_by_index = self.dataframe.columns.tolist().index("valuedatetime") + self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) + + self.annotations_grouped = self.__group_annotations() self.dataObjects = self.__merge_dataframe_with_annotations() + col = self.memDB.get_columns_with_annotations() - # columns = \ - # [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - # stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') - # for x, i in self.memDB.getEditColumns()] columns = \ [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') @@ -51,16 +49,27 @@ def init(self, memDB): self.SetObjectGetter(self.ObjectGetter) - self.SetItemCount(len(self.dataframe)) + self.SetItemCount(len(self.dataObjects)) def __merge_dataframe_with_annotations(self): - sort_by_index = self.dataframe.columns.tolist().index("valuedatetime") - self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) - data_list = self.dataframe.values.tolist() - anno_list = self.annotations.values.tolist() data = data_list + for key, value in self.annotations_grouped.iteritems(): + for i in range(0, len(data_list)): + if key in data[i]: + data[i].append(value) + break + + return data + + def __group_annotations(self): + """ + Ideally, this method should only be called once. Use self.grouped_annotations after calling this method + :return: + """ + anno_list = self.annotations.values.tolist() + anno = {} for i in range(0, len(anno_list)): value_id = anno_list[i][1] @@ -70,16 +79,10 @@ def __merge_dataframe_with_annotations(self): else: anno[value_id] = [annotation_code] - for key, value in anno.iteritems(): - for i in range(0, len(data_list)): - if key in data_list[i]: - data_list[i].append(value) - break - - return data + return anno def EnableSorting(self): - self.Bind(wx.EVT_LIST_COL_CLICK, self.onColSelected) + self.Bind(wx.EVT_LIST_COL_CLICK, self.on_column_selected) if not self.smallImageList: self.SetImageLists() if (not self.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and @@ -93,35 +96,34 @@ def ObjectGetter(self, index): """ return self.dataObjects[index % len(self.dataObjects)] - def onColSelected(self, evt): + def on_column_selected(self, event): """ Allows users to sort by clicking on columns """ - if isinstance(self.dataframe, pd.DataFrame): - if self.dataframe.empty: - return - else: - if not self.dataframe: - return + if not isinstance(self.dataframe, pd.DataFrame): + return + + if self.dataframe.empty: + return - logger.debug("Column: %s" % evt.m_col) - self.sortColumn(evt.m_col) + if not len(self.dataObjects): + return + + self.sortColumn(event.Column) def sortColumn(self, selected_column): oldSortColumnIndex = self.sortedColumnIndex self.sortedColumnIndex = selected_column - ascending = self.sortAscending - if ascending: - self.dataframe.sort_values(self.dataframe.columns[selected_column], inplace=True) - self.sortAscending = False - elif not ascending: - self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=False, inplace=True) - self.sortAscending = True + + self.sortAscending = not self.sortAscending + self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=self.sortAscending, inplace=True) self._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) - self.dataObjects = self.dataframe.values.tolist() - if self.GetItemCount: + # self.dataObjects = self.dataframe.values.tolist() + self.dataObjects = self.__merge_dataframe_with_annotations() + + if self.GetItemCount(): itemFrom = self.GetTopItem() itemTo = self.GetTopItem() + 1 + self.GetCountPerPage() itemTo = min(itemTo, self.GetItemCount() - 1) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 4a76710..9a98d30 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -378,16 +378,6 @@ def get_values(self, series_id=None): # newdf.drop(['_merge'], axis=1, inplace=True) # return df[df['valuedatetime'].isin(newdf['valuedatetime'])] - # def merge_annotation_with_timeseries_result(self, x): - # print x - # - # # return x - # return 123 - def merge_annotation_with_timeseries_result(self, data, anno): - - print 123 - return - def get_all_values_df(self): """ @@ -863,20 +853,24 @@ def get_quality_code(self): return self.read.getCVs(type="Quality Code") def get_annotation_by_code(self, code): - return self.read.getAnnotations(codes=[code])[0] + try: + return self.read.getAnnotations(codes=[code])[0] + except: + return None + def get_annotation_by_id(self, id): - return self.read.getAnnotations(ids=[id])[0] + try: + return self.read.getAnnotations(ids=[id])[0] + except: + return None + def get_all_annotations(self): - return self.read.getAnnotations(type=None) + try: + return self.read.getAnnotations(type=None) + except: + return None def get_annotations_by_result(self, resultid): - # setSchema(self._session_factory.engine) - - # ids = [x[0] for x in self.read._session.query(TimeSeriesResultValues.ValueID)\ - # .filter(TimeSeriesResultValues.ResultID == resultid).all()] - # q = self.read._session.query(TimeSeriesResultValueAnnotations)\ - # .filter(TimeSeriesResultValueAnnotations.ValueID.in_(ids)).all() - resultid = int(resultid) q = self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, From 91ac688c339d44746f1aae54dfb6584d43ccec13 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Fri, 16 Dec 2016 11:52:34 -0700 Subject: [PATCH 128/158] Silenced a error when clicking on annotations column. Its a patch for now Will fix soon --- odmtools/controller/olvDataTable.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index e4ff41d..ddbea37 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -112,6 +112,10 @@ def on_column_selected(self, event): self.sortColumn(event.Column) def sortColumn(self, selected_column): + if selected_column >= len(self.dataframe.columns): + # Cannot sort by annotation code yet. Need to fix this + return + oldSortColumnIndex = self.sortedColumnIndex self.sortedColumnIndex = selected_column From f5b17b7102017187950998a82f73659e764c6776 Mon Sep 17 00:00:00 2001 From: stephanie Date: Fri, 16 Dec 2016 15:25:35 -0700 Subject: [PATCH 129/158] update setup --- setup/Mac/ODMTools.spec | 20 ++++++++------------ setup/Mac/build.sh | 1 + setup/make.py | 6 +++--- setup/setup.py | 17 ++++++++++++----- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/setup/Mac/ODMTools.spec b/setup/Mac/ODMTools.spec index c009faf..0c4c2dc 100644 --- a/setup/Mac/ODMTools.spec +++ b/setup/Mac/ODMTools.spec @@ -5,33 +5,29 @@ block_cipher = None a = Analysis(['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'], pathex=['/Users/stephanie/DEV/ODMToolsPython/setup/Mac'], - binaries=None, - datas=None, hiddenimports=[], - hookspath=['/Users/stephanie/DEV/ODMToolsPython/setup/hooks'], + hookspath=None, runtime_hooks=None, - excludes=['PyQt4', 'PyQt4.QtCore', 'PyQt4.QtGui'], - win_no_prefer_redirects=False, - win_private_assemblies=False, + excludes=None, cipher=block_cipher) -pyz = PYZ(a.pure, a.zipped_data, +pyz = PYZ(a.pure, cipher=block_cipher) exe = EXE(pyz, a.scripts, exclude_binaries=True, name='ODMTools', debug=False, - strip=False, + strip=None, upx=True, - console=False , version='/Users/stephanie/DEV/ODMToolsPython/setup/version.txt', icon='/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns') + console=False , version='/Users/stephanie/DEV/ODMToolsPython/setup/version.txt', icon='odmtools/common/icons/ODMTools.icns') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, - strip=False, - upx=False, + strip=None, + upx=True, name='ODMTools') app = BUNDLE(coll, name='ODMTools.app', - icon='/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns', + icon='odmtools/common/icons/ODMTools.icns', bundle_identifier=None) diff --git a/setup/Mac/build.sh b/setup/Mac/build.sh index cbd8b59..240528e 100755 --- a/setup/Mac/build.sh +++ b/setup/Mac/build.sh @@ -7,5 +7,6 @@ if [ -d build ] && [ -d dist ]; then rm -ir build dist fi + sudo python setup.py py2app #sudo /usr/local/Cellar/python/2.7.8/bin/python setup.py py2app diff --git a/setup/make.py b/setup/make.py index b4ccbf6..bb4ecca 100644 --- a/setup/make.py +++ b/setup/make.py @@ -16,7 +16,7 @@ ## Update odmtools.meta.data whenever creating a release from odmtools.meta import data -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) SETUP_DIR = os.path.join(BASE_DIR, 'setup') WIN_DIR = os.path.join(SETUP_DIR, "Windows") MAC_DIR = os.path.join(SETUP_DIR, "Mac") @@ -32,7 +32,7 @@ WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") -APP_DIR = os.path.join(MAC_DIR, 'Dist', "ODMTools.app") +APP_DIR = os.path.join(MAC_DIR, 'Dist', "ODM2Tools.app") # Location of Windows files APP_FILE = os.path.join(BASE_DIR, "ODMTools.py") MAKE_FILE = os.path.realpath(__file__) @@ -94,7 +94,7 @@ def zipdir(basedir, archivename): z.write(absfn, zfn) def printInfo(): print "=============================================================" - print "= ODMTools Installer " + print "= ODM2Tools Installer " print "= Be sure to update odmtools/meta/data with every release " print "= Building release: {version}".format(version=data.version), print "\n= Platform: {platform}, {architecture}".format(platform=sys.platform, architecture=platform.architecture()), "\n=" diff --git a/setup/setup.py b/setup/setup.py index d61332f..18e3ea1 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -13,6 +13,7 @@ import sys +import os ''' from setuptools import setup @@ -23,23 +24,29 @@ -NAME = 'ODMTools' +NAME = 'ODM2Tools' +BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +ICON_DIR = os.path.join('odmtools', 'common', "icons") +WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") +MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") +#APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] +APP = os.path.join(BASE_DIR, 'ODMTools.py') extra_options = None sys.setrecursionlimit(2000) if sys.platform == 'darwin': sys.argv.append('py2app') from setuptools import setup - APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] + # APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib'] - OPTIONS = {'iconfile': '/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns', + OPTIONS = {'iconfile': MAC_ICON_FILE, 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], 'frameworks': LIBS} extra_options = dict(app=APP, setup_requires=['py2app'], options={'py2app': OPTIONS}) elif sys.platform == 'win32': sys.argv.append('py2exe') from distutils.core import setup - APP = ['C:\Users\Jacob\Documents\ODMToolsPython\ODMTools.py'] + #APP = ['C:\Users\Jacob\Documents\ODMToolsPython\ODMTools.py'] import numpy import py2exe from glob import glob @@ -68,7 +75,7 @@ sys.path.append("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\Microsoft.VC90.CRT") - sys.path.append("C:\\Users\\Jacob\\Documents\\ODMToolsPython") + sys.path.append(BASE_DIR) extra_options = dict(console=APP, data_files=data_files, options={'py2exe': OPTIONS}) setup(name=NAME, **extra_options) From 4cac7d9152200f02be0a69481c276d652c4e201d Mon Sep 17 00:00:00 2001 From: sreeder Date: Fri, 16 Dec 2016 15:33:47 -0700 Subject: [PATCH 130/158] set schema --- odmtools/odmdata/memory_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 1bdc792..79908ad 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -80,7 +80,7 @@ def getDataValuesDF(self): def get_annotations(self, query_db_again=False): # self.mem_service._session.commit() - # setSchema(self.mem_service._session_factory.engine) + setSchema(self.series_service._session_factory.engine) if self.results_annotations is None or query_db_again: result_id = self.df.resultid[0] annotation = self.series_service.get_annotations_by_result(resultid=result_id) From d8a0bc9c2c976966449e50db2be8443937e567aa Mon Sep 17 00:00:00 2001 From: sreeder Date: Fri, 16 Dec 2016 16:23:37 -0700 Subject: [PATCH 131/158] update paths in setup file --- setup/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup/setup.py b/setup/setup.py index 18e3ea1..897482c 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -46,15 +46,15 @@ elif sys.platform == 'win32': sys.argv.append('py2exe') from distutils.core import setup - #APP = ['C:\Users\Jacob\Documents\ODMToolsPython\ODMTools.py'] + APP = ['D:\Dev\ODMTools\ODMTools.py'] import numpy import py2exe from glob import glob data_files = [ ("Microsoft.VC90.CRT", glob(r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\Microsoft.VC90.CRT\*.*')), - (r'mpl-data', [r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), - (r'mpl-data\images', glob(r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), - (r'mpl-data\fonts', glob(r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*'))] + (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), + (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), + (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*'))] OPTIONS = { #'excludes': ['_ssl', 'pyreadline', 'difflib', 'doctest', 'optparse', 'pickle', 'calendar'], @@ -70,7 +70,7 @@ "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'ObjectListView', 'pyodbc'], + "packages": ['wx.lib.pubsub', 'pyodbc'], #'ObjectListView', } From e7548d9be8adbd2f69265d07046266a8e7b14fb6 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 19 Dec 2016 12:01:35 -0700 Subject: [PATCH 132/158] update setup stuff --- setup/setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup/setup.py b/setup/setup.py index 897482c..2342a5c 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -54,7 +54,8 @@ ("Microsoft.VC90.CRT", glob(r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\Microsoft.VC90.CRT\*.*')), (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), - (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*'))] + (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] OPTIONS = { #'excludes': ['_ssl', 'pyreadline', 'difflib', 'doctest', 'optparse', 'pickle', 'calendar'], @@ -70,7 +71,7 @@ "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'pyodbc'], #'ObjectListView', + "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'geoalchemy2'], #'ObjectListView', } From ba5c068e78c55e4b8fc9f1c426ee735cbd3fe9b5 Mon Sep 17 00:00:00 2001 From: sreeder Date: Mon, 19 Dec 2016 16:03:54 -0700 Subject: [PATCH 133/158] update setup file --- setup/Windows/ODMTools.spec | 44 +++++-------------------------------- setup/make.py | 2 +- setup/setup.py | 8 +++++-- 3 files changed, 12 insertions(+), 42 deletions(-) diff --git a/setup/Windows/ODMTools.spec b/setup/Windows/ODMTools.spec index b375c2e..cf6bc60 100644 --- a/setup/Windows/ODMTools.spec +++ b/setup/Windows/ODMTools.spec @@ -1,44 +1,10 @@ - - -a = Analysis(['D:\\DEV\\ODMTools\\ODMTools.py'], - pathex=['D:\\DEV\\ODMTools\\setup\\Windows'], - binaries=None, - datas=None, - hiddenimports=[], - hookspath=['../hooks'], - runtime_hooks=None, - excludes=['PyQt4', 'PyQt4.QtCore', 'PyQt4.QtGui'], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - exclude_binaries=True, - name='ODMTools', - debug=False, - strip=False, - upx=False, - console=True , version='D:\\DEV\\ODMTools\\setup\\version.txt', icon='D:\\DEV\\ODMTools\odmtools\\common\\icons\\ODMTools.ico') -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - name='ODM2Tools') - - - - # -*- mode: python -*- block_cipher = None -a = Analysis(['ODMTools.py'], - pathex=['D:\\DEV\\ODMTools'], +a = Analysis(['D:\\DEV\\ODMTools\\ODMTools.py'], + pathex=['D:\\DEV\\ODMTools\\setup\\Windows'], binaries=None, datas=None, hiddenimports=[], @@ -56,12 +22,12 @@ exe = EXE(pyz, name='ODMTools', debug=False, strip=False, - upx=True, - console=True ) + upx=False, + console=True , version='D:\\DEV\\ODMTools\\setup\\version.txt', icon='D:\\DEV\\ODMTools\\odmtools\\common\\icons\\ODMTools.ico') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, - upx=True, + upx=False, name='ODMTools') diff --git a/setup/make.py b/setup/make.py index bb4ecca..e5752c0 100644 --- a/setup/make.py +++ b/setup/make.py @@ -28,7 +28,7 @@ MAC_WORK_DIR = os.path.join(MAC_DIR, "Temp") WORK_DIR = os.path.join(WIN_DIR, "Temp") -ICON_DIR = os.path.join('odmtools', 'common', "icons") +ICON_DIR = os.path.join(BASE_DIR, 'odmtools', 'common', "icons") WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") diff --git a/setup/setup.py b/setup/setup.py index 2342a5c..bc398d1 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -55,7 +55,11 @@ (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), - (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*')), + ('.', glob('*.dll')), + ('.', glob('C:\Windows\system32\OPENGL32.dll'))] + #('.', glob('mkl_p4m.dll')), + #('.', glob('mkl_p4.dll'))] OPTIONS = { #'excludes': ['_ssl', 'pyreadline', 'difflib', 'doctest', 'optparse', 'pickle', 'calendar'], @@ -71,7 +75,7 @@ "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'geoalchemy2'], #'ObjectListView', + "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas'], #'ObjectListView', } From 356b8e3710fb98cc8797ca7271502c1755301312 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 19 Dec 2016 17:39:32 -0700 Subject: [PATCH 134/158] try to update setup file for mac settings --- setup/Mac/build.sh | 10 ++++++---- setup/setup.py | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/setup/Mac/build.sh b/setup/Mac/build.sh index 240528e..5192562 100755 --- a/setup/Mac/build.sh +++ b/setup/Mac/build.sh @@ -2,11 +2,13 @@ echo "Building!!" +echo "cleanup" if [ -d build ] && [ -d dist ]; then echo "Cleaning up old build and dist files" - rm -ir build dist + rm -r --interactive=once build dist fi - - -sudo python setup.py py2app +echo "activate environment" +source activate odmtools +echo "run py2app" +sudo python ../setuptest.py py2app #sudo /usr/local/Cellar/python/2.7.8/bin/python setup.py py2app diff --git a/setup/setup.py b/setup/setup.py index bc398d1..f9848be 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -26,7 +26,7 @@ NAME = 'ODM2Tools' BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -ICON_DIR = os.path.join('odmtools', 'common', "icons") +ICON_DIR = os.path.join(BASE_DIR, 'odmtools', 'common', "icons") WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") @@ -55,9 +55,9 @@ (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), - (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*')), - ('.', glob('*.dll')), - ('.', glob('C:\Windows\system32\OPENGL32.dll'))] + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] + # ('.', glob('*.dll')), + # ('.', glob('C:\Windows\system32\OPENGL32.dll'))] #('.', glob('mkl_p4m.dll')), #('.', glob('mkl_p4.dll'))] @@ -69,6 +69,7 @@ 'MSVCP90.dll', 'WS2_32.dll', 'WINSPOOL.DRV', 'GDI32.dll', 'KERNEL32.dll', 'ntdll.dll', 'COMCTL32.dll', 'COMDLG32.dll', 'msvcrt.dll', 'RPCRT4.dll'], "optimize": 2, + "frameworks": ['C:\Windows\system32\OPENGL32.dll'','',''] "bundle_files": 3, "dist_dir": "dist", "xref": False, From b88afd820c4bb6da9c3a021578ce5d30dcae446e Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 20 Dec 2016 16:47:27 -0700 Subject: [PATCH 135/158] WORKING py2exe --- setup/setup.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/setup/setup.py b/setup/setup.py index f9848be..4a6f447 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -40,7 +40,8 @@ # APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib'] OPTIONS = {'iconfile': MAC_ICON_FILE, - 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], 'frameworks': LIBS} + 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], + 'frameworks': LIBS} extra_options = dict(app=APP, setup_requires=['py2app'], options={'py2app': OPTIONS}) elif sys.platform == 'win32': @@ -53,9 +54,9 @@ data_files = [ ("Microsoft.VC90.CRT", glob(r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\Microsoft.VC90.CRT\*.*')), (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), - (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), - (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), - (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] + (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\images\*.*')), + (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] # ('.', glob('*.dll')), # ('.', glob('C:\Windows\system32\OPENGL32.dll'))] #('.', glob('mkl_p4m.dll')), @@ -69,14 +70,16 @@ 'MSVCP90.dll', 'WS2_32.dll', 'WINSPOOL.DRV', 'GDI32.dll', 'KERNEL32.dll', 'ntdll.dll', 'COMCTL32.dll', 'COMDLG32.dll', 'msvcrt.dll', 'RPCRT4.dll'], "optimize": 2, - "frameworks": ['C:\Windows\system32\OPENGL32.dll'','',''] + # "includes": ['C:\Windows\system32\OPENGL32.dll', + # 'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4.dll', + # 'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4m.dll'], "bundle_files": 3, "dist_dir": "dist", "xref": False, "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas'], #'ObjectListView', + "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas', 'mkl'], #'ObjectListView', } From 6bc2b8ea0629fb67a2088ff8a68e1b00099578ca Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 21 Dec 2016 17:08:06 -0700 Subject: [PATCH 136/158] add mkl dll through code --- setup/setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup/setup.py b/setup/setup.py index 4a6f447..5ca76d3 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -56,11 +56,11 @@ (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\images\*.*')), (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), - (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*'))] + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*')), # ('.', glob('*.dll')), # ('.', glob('C:\Windows\system32\OPENGL32.dll'))] - #('.', glob('mkl_p4m.dll')), - #('.', glob('mkl_p4.dll'))] + ('.', glob(r'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4m.dll')), + ('.', glob(r'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4.dll'))] OPTIONS = { #'excludes': ['_ssl', 'pyreadline', 'difflib', 'doctest', 'optparse', 'pickle', 'calendar'], @@ -79,7 +79,8 @@ "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas', 'mkl'], #'ObjectListView', + "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas'], #'ObjectListView', + #make sure that mkl_p4.dll and mkl_p4m.dll have been copied into the Dist folder } From b88f20e2c5f876ec53d4aae9297d29ce84287fac Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 28 Dec 2016 12:40:16 -0700 Subject: [PATCH 137/158] Sort by annotation --- odmtools/controller/olvDataTable.py | 31 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index ddbea37..d3f0029 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -112,20 +112,18 @@ def on_column_selected(self, event): self.sortColumn(event.Column) def sortColumn(self, selected_column): - if selected_column >= len(self.dataframe.columns): - # Cannot sort by annotation code yet. Need to fix this - return + self.sortAscending = not self.sortAscending oldSortColumnIndex = self.sortedColumnIndex self.sortedColumnIndex = selected_column - self.sortAscending = not self.sortAscending - self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=self.sortAscending, inplace=True) - self._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) - # self.dataObjects = self.dataframe.values.tolist() - self.dataObjects = self.__merge_dataframe_with_annotations() + if selected_column >= len(self.dataframe.columns): + self.dataObjects = self.sort_columns_by_annotation_code(reverse=self.sortAscending) + else: + self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=self.sortAscending, inplace=True) + self.dataObjects = self.__merge_dataframe_with_annotations() if self.GetItemCount(): itemFrom = self.GetTopItem() @@ -133,6 +131,23 @@ def sortColumn(self, selected_column): itemTo = min(itemTo, self.GetItemCount() - 1) self.RefreshItems(itemFrom, itemTo) + def sort_columns_by_annotation_code(self, reverse=False): + rows_with_annotation = [] + rows_without_annotation = [] + + column_number_of_dataframe = len(self.dataframe.columns) + + for i in self.dataObjects: + if len(i) > column_number_of_dataframe: + rows_with_annotation.append(i) + else: + rows_without_annotation.append(i) + + if reverse: + return rows_without_annotation + rows_with_annotation + else: + return rows_with_annotation + rows_without_annotation + def onItemSelected(self, event): """ From 15e4b4b13ace308a8a2fcc87a6a4def30d964531 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Wed, 28 Dec 2016 14:17:35 -0700 Subject: [PATCH 138/158] Bug fix when selecting a different result It was not making a query to the db when showing a different row. Thats fixed now. --- odmtools/odmdata/memory_database.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 1bdc792..5a17727 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -44,18 +44,14 @@ def __init__(self, taskserver=None): #self.annotation_list = pd.DataFrame() columns =['ResultID', 'ValueDateTime', 'ValueID', 'AnnotationID') #send in engine - def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") setSchema(self.mem_service._session_factory.engine) - def set_series_service(self, service): self.series_service = service - - ############## # DB Queries ############## @@ -78,14 +74,10 @@ def getDataValuesDF(self): logging.debug("done updating memory dataframe") return self.df - def get_annotations(self, query_db_again=False): - # self.mem_service._session.commit() - # setSchema(self.mem_service._session_factory.engine) - if self.results_annotations is None or query_db_again: - result_id = self.df.resultid[0] - annotation = self.series_service.get_annotations_by_result(resultid=result_id) - self.results_annotations = annotation - + def get_annotations(self): + result_id = self.df.resultid[0] + annotation = self.series_service.get_annotations_by_result(resultid=result_id) + self.results_annotations = annotation return self.results_annotations def getDataValues(self): From 53ecd865369822beb19272fa981dab0f590b9157 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 2 Jan 2017 11:44:49 -0700 Subject: [PATCH 139/158] #273 Fixed the ribbons not highlighting correctly when hovering on and off them --- odmtools/gui/mnuRibbon.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py index 6c21874..eede1aa 100644 --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -129,6 +129,7 @@ def _init_ctrls(self, prnt): # ------------------------------------------------------------------------------- editPage = RB.RibbonPage(self, wx.ID_ANY, "Edit") + # editPage.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) main_panel = RB.RibbonPanel(editPage, wx.ID_ANY, "Main", wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, RB.RIBBON_PANEL_NO_AUTO_MINIMISE) @@ -204,14 +205,28 @@ def _init_ctrls(self, prnt): self.CurrPage = 1 self.SetActivePageByIndex(self.CurrPage) - self.bindEvents() + self.__bind_events() self.initPubSub() def __init__(self, parent, id, name): self.parent = parent self._init_ctrls(parent) - def bindEvents(self): + def on_mouse_enter(self, event): + ribbon_panel = event.GetEventObject().GetParent() + ribbon_panel._hovered = True + + self.Refresh() + event.Skip() + + def on_mouse_leave(self, event): + ribbon_panel = event.GetEventObject().GetParent() + ribbon_panel._hovered = False + + self.Refresh() + event.Skip() + + def __bind_events(self): ###Docking Window Selection self.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.onDocking, id=wxID_RIBBONVIEWTABLE) self.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.onDocking, id=wxID_RIBBONVIEWSERIES) @@ -262,6 +277,24 @@ def bindEvents(self): ###Ribbon Event self.Bind(RB.EVT_RIBBONBAR_PAGE_CHANGED, self.onFileMenu, id=wxID_PANEL1) + # ENTER + self.main_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 1 + self.edit_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 2 + self.record_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 3 + self.PlotsOptions_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 4 + self.plots_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 5 + self.dateTime_buttonbar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 6 + self.scriptBar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 7 + + # LEAVE + self.main_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 1 + self.edit_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 2 + self.record_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 3 + self.PlotsOptions_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 4 + self.plots_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 5 + self.dateTime_buttonbar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 6 + self.scriptBar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 7 + def initPubSub(self): Publisher.subscribe(self.toggleEditButtons, "EnableEditButtons") Publisher.subscribe(self.enableButtons, "EnablePlotButtons") From ea3325b1df7581a4af713d0c4844ba7710e23365 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 9 Jan 2017 15:46:25 -0700 Subject: [PATCH 140/158] add setup info for make .ap file --- setup/setup.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/setup/setup.py b/setup/setup.py index 5ca76d3..d872c03 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -11,6 +11,20 @@ python setup.py py2exe """ +import macholib +#print("~"*60 + "macholib verion: "+macholib.__version__) +if macholib.__version__ <= "1.7": + print("Applying macholib patch...") + import macholib.dyld + import macholib.MachOGraph + dyld_find_1_7 = macholib.dyld.dyld_find + def dyld_find(name, loader=None, **kwargs): + #print("~"*60 + "calling alternate dyld_find") + if loader is not None: + kwargs['loader_path'] = loader + return dyld_find_1_7(name, **kwargs) + macholib.MachOGraph.dyld_find = dyld_find + import sys import os @@ -38,7 +52,7 @@ sys.argv.append('py2app') from setuptools import setup # APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] - LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib'] + LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib', '/anaconda/lib/libwx_osx_cocoau-3.0.0.0.0.dylib'] OPTIONS = {'iconfile': MAC_ICON_FILE, 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], 'frameworks': LIBS} From 855cf173489880e67a8fb43949f609509aa652e6 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 9 Jan 2017 15:48:48 -0700 Subject: [PATCH 141/158] anno, set schema --- odmtools/odmdata/memory_database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 5a17727..2d9d24e 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -76,6 +76,7 @@ def getDataValuesDF(self): def get_annotations(self): result_id = self.df.resultid[0] + setSchema(self.series_service._session_factory.engine) annotation = self.series_service.get_annotations_by_result(resultid=result_id) self.results_annotations = annotation return self.results_annotations From 89e9c09c9439c2ea8d5e118b157b26fb19713469 Mon Sep 17 00:00:00 2001 From: Francisco Arrieta Date: Mon, 9 Jan 2017 17:25:07 -0700 Subject: [PATCH 142/158] #309 Fixed the date formating error --- odmtools/controller/olvSeriesSelector.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index e408768..a396e98 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -47,18 +47,19 @@ def onKeyPress(self, evt): pass def _buildColumns(self, columns): - seriesColumns = [ - ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, - # stringConverter = '%s') - stringConverter='%Y-%m-%d %H:%M:%S' if ("date" in key.lower()) else '%s') - for key in columns] + series_columns = [] + for key in columns: + col = ColumnDefn(title=key, + minimumWidth=100, + valueGetter=key, + stringConverter=date_to_string if ("date" in key.lower()) else '%s') + series_columns.append(col) - self.SetColumns(seriesColumns) + self.SetColumns(series_columns) self.CreateCheckStateColumn() """User can select series_service using the mouse to click on check boxes """ - def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): """ This is the same code, just added the original _HandleLeftDownOnImage in ObjectListView but @@ -109,6 +110,11 @@ def GetModelObjects(self): return self._modelObjects if self._modelObjects else [] +def date_to_string(value): + try: + return value.strftime("%Y-%m-%d %H:%M:%S") + except AttributeError: + return "" From 7560412c762c008c2bfbf0d9f5d1e72791e94397 Mon Sep 17 00:00:00 2001 From: stephanie Date: Thu, 19 Jan 2017 14:59:13 -0700 Subject: [PATCH 143/158] build action from scratch --- odmtools/odmservices/edit_service.py | 34 +++++++++++------ odmtools/odmservices/series_service.py | 51 ++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index fa65edc..2842e9d 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -566,7 +566,7 @@ def getResult(self, var, meth, proc, action, action_by): values = self.memDB.getDataValuesDF() # copy old result = self.memDB.series_service.get_series(str(values["resultid"][0])) - + newaction = Actions() # change var, meth proc, in df #intend ts, agg sta @@ -579,24 +579,33 @@ def getResult(self, var, meth, proc, action, action_by): result.ProcessingLevelObj = proc if meth: - action.MethodID = meth.MethodID - action.MethodObj = meth.MethodObj + newaction.MethodID = meth.MethodID + newaction.MethodObj = meth.MethodObj #if result does not exist if not self.memDB.series_service.resultExists(result): try: #create Action - action.ActionID = None - action.ActionTypeCV = "Derivation" - self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) - self.memDB.series_service.read._session.expunge(action.MethodObj) - action = self.memDB.series_service.create.createAction(action) # it times out. find out why - print action + # self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) + # self.memDB.series_service.read._session.expunge(action.MethodObj) + + newaction.ActionDescription = action.ActionDescription + newaction.ActionFileLink = action.ActionFileLink + newaction.BeginDateTime = action.BeginDateTime + newaction.BeginDateTimeUTCOffset = action.BeginDateTimeUTCOffset + newaction.EndDateTime = action.EndDateTime + newaction.EndDateTimeUTCOffset = action.EndDateTimeUTCOffset + newaction.MethodID = action.MethodID + newaction.ActionTypeCV = "Derivation" + + print newaction + newaction = self.memDB.series_service.create.createAction(newaction) # it times out. find out why + print newaction # create Actionby done - action_by.ActionID = action.ActionID + action_by.ActionID = newaction.ActionID action_by= self.memDB.series_service.create.createActionby(action_by) print action_by @@ -607,8 +616,8 @@ def getResult(self, var, meth, proc, action, action_by): feature_action = FeatureActions() feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID - feature_action.ActionID = action.ActionID - feature_action.ActionObj = action + feature_action.ActionID = newaction.ActionID + feature_action.ActionObj = newaction feature_action.SamplingFeatureObj = sampling_feature feature_action = self.memDB.series_service.create.createFeatureAction(feature_action) print feature_action @@ -631,6 +640,7 @@ def getResult(self, var, meth, proc, action, action_by): result = self.memDB.series_service.create.createResult(result) print result except Exception as ex: + self.memDB.series_service._session.rollback() print ex return self.updateResult(result) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index aec7c69..47fb187 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -3,7 +3,7 @@ from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -from odmtools.odmservices.to_sql_newrows import get_insert, get_delete, get_update +#from odmtools.odmservices.to_sql_newrows import get_insert, get_delete, get_update import datetime from odmtools.common.logger import LoggerTool import pandas as pd @@ -646,14 +646,14 @@ def _get_df_query(self, values): def upsert_values(self, values): setSchema(self._session_factory.engine) query = self._get_df_query(values) - newvals= get_insert(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + newvals= get_insert(df= values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) if not newvals.empty: self.insert_values(newvals) - delvals = get_delete(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + delvals = get_delete(df= values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) if not delvals.empty: self.delete_dvs(delvals["valuedatetime"].tolist()) - upvals = get_update(df = values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + upvals = get_update(df= values, query= query, dup_cols = ["valuedatetime", "resultid"], engine= self._session_factory.engine) if not upvals.empty: self.update_values(upvals) @@ -981,3 +981,46 @@ def get_values_by_series(self, series_id): q = q.order_by(TimeSeriesResultValues.ValueDateTime) return q.all() + + + def get_delete(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + + def get_update(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) + #newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + test = newdf[newdf['datavalue_x'] != newdf['datavalue_y']] + return df[df['valuedatetime'].isin(test['valuedatetime'])] + + def get_insert(df, engine, query, dup_cols=[]): + """ + Remove rows from a dataframe that already exist in a database + Required: + df : dataframe to remove duplicate rows from + engine: SQLAlchemy engine object + tablename: tablename to check duplicates in + dup_cols: list or tuple of column names to check for duplicate row values + Optional: + filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter + can be either a datetime, int, or float data type + useful for restricting the database table size to check + filter_categorical_col : the name of the categorical data column for Where = value check + Creates an "IN ()" check on the unique values in this column + Returns + Unique list of values from dataframe compared to database table + """ + + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'left_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] \ No newline at end of file From 23d33516b28c6d4d870b783a96eedb5d799edd0c Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Fri, 20 Jan 2017 12:52:00 -0700 Subject: [PATCH 144/158] try/catch for datetime issue --- odmtools/odmservices/edit_service.py | 45 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index fa65edc..4106a67 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -498,20 +498,23 @@ def restore(self): self.reset_filter() def save(self, result=None): - values = self.memDB.getDataValuesDF() - - if not result: - result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) - else: - values["resultid"] = result.ResultID + try: + values = self.memDB.getDataValuesDF() - # update result - result.ValueCount = 0 - self.updateResult(result) - # upsert values - self.memDB.series_service.upsert_values(values) - # save new annotations - self.add_annotations(self.memDB.annotation_list) + if not result: + result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) + else: + values["resultid"] = result.ResultID + + # update result + result.ValueCount = 0 + self.updateResult(result) + # upsert values + self.memDB.series_service.upsert_values(values) + # save new annotations + self.add_annotations(self.memDB.annotation_list) + except Exception as e: + logger.error("Exception encountered while saving: {}".format(e)) return result def save_existing(self, result): @@ -651,8 +654,8 @@ def updateResult(self, result, valuecount = -10): setSchema(self.memDB.series_service._session_factory.engine) self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) - self.memDB.series_service.update.updateAction(actionID=action.ActionID, begin=action.BeginDateTime, end=action.EndDateTime) - + self.memDB.series_service.update.updateAction(actionID=action.ActionID, + begin=action.BeginDateTime, end=action.EndDateTime) return result def overlapcalc(self, result, values, overwrite): @@ -678,16 +681,12 @@ def add_annotations(self, annolist): #get df with only ValueID and AnnotationID #remove any duplicates #save df to db - pass - - - - - - - + print("ANNOTATIONS ARE ATTEMPTED TO ADD") + query = "SELECT resultid, datetime FROM TSRV" + pd.read_sql(query, self._session_factory.engine) + pass # From 0ca002a4a4d967343a2e350a047c473d9f4a0b94 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 24 Jan 2017 11:59:22 -0700 Subject: [PATCH 145/158] only connect to an odm2 database --- odmtools/view/clsDBConfig.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/odmtools/view/clsDBConfig.py b/odmtools/view/clsDBConfig.py index 3e6231b..d67854a 100644 --- a/odmtools/view/clsDBConfig.py +++ b/odmtools/view/clsDBConfig.py @@ -172,8 +172,9 @@ def __init__(self, parent): self.stVersion.Wrap(-1) connectionSizer.Add(self.stVersion, 0, wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, 5) - version_choices = [ u"1.1", u"2.0"] - self.cbVersion = wx.ComboBox(self, wx.ID_ANY, u"1.1", wx.DefaultPosition, wx.DefaultSize, + # version_choices = [ u"1.1", u"2.0"] + version_choices = [u"2.0"] + self.cbVersion = wx.ComboBox(self, wx.ID_ANY, u"2.0", wx.DefaultPosition, wx.DefaultSize, version_choices, wx.CB_READONLY )#| wx.CB_SORT) self.cbVersion.SetSelection(0) connectionSizer.Add(self.cbVersion, 1, wx.ALL | wx.EXPAND, 5) From 714af29b8e17cd8c733db04983484f08284eddce Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 24 Jan 2017 11:59:58 -0700 Subject: [PATCH 146/158] update text for dbconn form --- odmtools/view/clsDBConfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odmtools/view/clsDBConfig.py b/odmtools/view/clsDBConfig.py index d67854a..8c3d0e8 100644 --- a/odmtools/view/clsDBConfig.py +++ b/odmtools/view/clsDBConfig.py @@ -179,7 +179,7 @@ def __init__(self, parent): self.cbVersion.SetSelection(0) connectionSizer.Add(self.cbVersion, 1, wx.ALL | wx.EXPAND, 5) - self.stConnType = wx.StaticText(self, wx.ID_ANY, u"Connection Type:", wx.DefaultPosition, wx.DefaultSize, + self.stConnType = wx.StaticText(self, wx.ID_ANY, u"ODM Version:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stConnType.Wrap(-1) connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) From 28d94f1885a4979a326b58e6d4348d73dc378b60 Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 25 Jan 2017 12:22:16 -0700 Subject: [PATCH 147/158] add functionality to save annotations to the db --- odmtools/odmdata/memory_database.py | 10 +++--- odmtools/odmservices/edit_service.py | 47 ++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 2d9d24e..7acc827 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -190,11 +190,11 @@ def chunking(self, data): def updateFlag(self, ids, value): - flags = pd.DataFrame(columns = ['AnnotationID', 'DateTime', 'ResultID', 'ValueID']) - flags["DateTime"] = ids - flags["AnnotationID"] = value - flags["ResultID"] = self.series.ResultID - flags["ValueID"] = None + flags = pd.DataFrame(columns = ['annotationid', 'valuedatetime', 'resultid', 'valueid']) + flags["valuedatetime"] = ids + flags["annotationid"] = value + flags["resultid"] = self.series.ResultID + flags["valueid"] = None #what if the column already exists diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 4106a67..9f0e9ba 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -678,15 +678,50 @@ def overlapcalc(self, result, values, overwrite): def add_annotations(self, annolist): #match up with existing values and get value id - #get df with only ValueID and AnnotationID - #remove any duplicates - #save df to db + print("ANNOTATIONS ARE ATTEMPTED TO ADD") - query = "SELECT resultid, datetime FROM TSRV" - pd.read_sql(query, self._session_factory.engine) + engine = self.memDB.series_service._session_factory.engine + + q =self.memDB.series_service._session.query(TimeSeriesResultValues) \ + .filter(TimeSeriesResultValues.ResultID == int(min(annolist["resultid"]))) + + query = q.statement.compile(dialect=engine.dialect) + # data = pd.read_sql_query(sql=query, con=self._session_factory.engine, + # params=query.params) + # query = "SELECT ValueID, ResultID, ValueDateTime FROM TimeSeriesResultValues Where ResultID="+annolist["ResultID"][0] + + vals = pd.read_sql_query(sql=query, con=engine,params=query.params) + # remove any duplicates + annolist.drop_duplicates(["resultid", "annotationid", "valuedatetime"], keep= 'last', inplace = True) + newdf = pd.merge(annolist, vals, how='left', on= ["resultid", "valuedatetime"], indicator = True) + print "after merge" + print newdf + # newdf = newdf(newdf['_merge'], axis = 1, inplace = True) + # print "after filter" + # print newdf + + #get only AnnotationID and ValueID + print "after column selection" + mynewdf= newdf[["valueid_y","annotationid"]] + print "rename columns" + mynewdf.columns = ["ValueID", "AnnotationID"] + print newdf + + + # save df to db + print "save to db" + self.memDB.series_service.add_annotations(mynewdf) + print "done" + + + # df.drop_duplicates(dup_cols, keep='last', inplace=True) + # newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + # newdf = newdf[newdf['_merge'] == 'left_only'] + # newdf.drop(['_merge'], axis=1, inplace=True) + # return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + - pass # From 51fdff12ba751498adcf9e564fdf734833291196 Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 25 Jan 2017 12:23:44 -0700 Subject: [PATCH 148/158] remove comments. fix issue #305 --- odmtools/odmservices/edit_service.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 9f0e9ba..fa40508 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -694,31 +694,14 @@ def add_annotations(self, annolist): # remove any duplicates annolist.drop_duplicates(["resultid", "annotationid", "valuedatetime"], keep= 'last', inplace = True) newdf = pd.merge(annolist, vals, how='left', on= ["resultid", "valuedatetime"], indicator = True) - print "after merge" - print newdf - # newdf = newdf(newdf['_merge'], axis = 1, inplace = True) - # print "after filter" - # print newdf + #get only AnnotationID and ValueID - print "after column selection" mynewdf= newdf[["valueid_y","annotationid"]] - print "rename columns" mynewdf.columns = ["ValueID", "AnnotationID"] - print newdf - # save df to db - print "save to db" self.memDB.series_service.add_annotations(mynewdf) - print "done" - - - # df.drop_duplicates(dup_cols, keep='last', inplace=True) - # newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) - # newdf = newdf[newdf['_merge'] == 'left_only'] - # newdf.drop(['_merge'], axis=1, inplace=True) - # return df[df['valuedatetime'].isin(newdf['valuedatetime'])] From cad173cbb871c306634feb01f4072d952c5d7a26 Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 25 Jan 2017 12:36:43 -0700 Subject: [PATCH 149/158] update versions --- odmtools/meta/data.py | 2 +- setup/Windows/odmtools_console.iss | 4 ++-- setup/Windows/odmtools_no_console.iss | 4 ++-- setup/Windows/odmtools_setup.iss | 2 +- setup/Windows/odmtools_setup_build.iss | 2 +- setup/Windows/test.iss | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/odmtools/meta/data.py b/odmtools/meta/data.py index 45c8988..997f9fc 100644 --- a/odmtools/meta/data.py +++ b/odmtools/meta/data.py @@ -1,5 +1,5 @@ app_name = "ODM2Tools" -version = "2.0.0_Beta" +version = "2.0.1_Beta" copyright = "Copyright (c) 2013 - 2015, Utah State University. All rights reserved." description = "ODMTools is a python application for managing observational data using the Observations Data Model. " \ "ODMTools allows you to query, visualize, and edit data stored in an Observations Data Model (ODM) database." \ diff --git a/setup/Windows/odmtools_console.iss b/setup/Windows/odmtools_console.iss index 1190aca..4899e45 100644 --- a/setup/Windows/odmtools_console.iss +++ b/setup/Windows/odmtools_console.iss @@ -3,8 +3,8 @@ #define MyAppName "ODM2Tools" -#define MyAppVersion "2.0.0_Beta" -#define MyAppExeLongName "ODM2Tools_2.0.0_Beta_win32_x86_64_console.exe" +#define MyAppVersion "2.0.1_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.1_Beta_win32_x86_64_console.exe" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" #define MyAppExeName "ODM2Tools.exe" diff --git a/setup/Windows/odmtools_no_console.iss b/setup/Windows/odmtools_no_console.iss index 5f98bcc..adde9b8 100644 --- a/setup/Windows/odmtools_no_console.iss +++ b/setup/Windows/odmtools_no_console.iss @@ -4,8 +4,8 @@ #define MyAppName "ODMTools" -#define MyAppExeLongName "ODM2Tools_2.0.0_Beta_win32_x86_64.exe" -#define MyAppVersion "2.0.0_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.1_Beta_win32_x86_64.exe" +#define MyAppVersion "2.0.1_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" diff --git a/setup/Windows/odmtools_setup.iss b/setup/Windows/odmtools_setup.iss index a62d90f..a035311 100644 --- a/setup/Windows/odmtools_setup.iss +++ b/setup/Windows/odmtools_setup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "ODM2Tools" -#define MyAppVersion "2.0.0_Beta" +#define MyAppVersion "2.0.1_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" #define MyAppExeName "ODM2Tools.exe" diff --git a/setup/Windows/odmtools_setup_build.iss b/setup/Windows/odmtools_setup_build.iss index ef66116..c3b5278 100644 --- a/setup/Windows/odmtools_setup_build.iss +++ b/setup/Windows/odmtools_setup_build.iss @@ -5,7 +5,7 @@ #define MyAppName "ODM2Tools" #define OrgName "UCHIC" -#define MyAppVersion "v2.0.0-beta" +#define MyAppVersion "v2.0.1-beta" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" [Setup] diff --git a/setup/Windows/test.iss b/setup/Windows/test.iss index 12911f6..a420b09 100644 --- a/setup/Windows/test.iss +++ b/setup/Windows/test.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "ODM2Tools" -#define MyAppVersion "2.0.0" +#define MyAppVersion "2.0.1" #define MyAppPublisher "My Company, Inc." #define MyAppURL "http://www.example.com/" #define MyAppExeName "ODM2Tools_console.exe" From e8b43fc09df930638ad18f713f7844809df33a5c Mon Sep 17 00:00:00 2001 From: sreeder Date: Wed, 25 Jan 2017 14:12:28 -0700 Subject: [PATCH 150/158] test before accessing anootations --- odmtools/controller/olvDataTable.py | 39 +++++----- odmtools/odmdata/memory_database.py | 1 + odmtools/odmservices/edit_service.py | 101 +++++++++++++++---------- odmtools/odmservices/series_service.py | 7 +- 4 files changed, 86 insertions(+), 62 deletions(-) diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index d3f0029..1b4ff44 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -54,12 +54,12 @@ def init(self, memDB): def __merge_dataframe_with_annotations(self): data_list = self.dataframe.values.tolist() data = data_list - - for key, value in self.annotations_grouped.iteritems(): - for i in range(0, len(data_list)): - if key in data[i]: - data[i].append(value) - break + if self.annotations_grouped: + for key, value in self.annotations_grouped.iteritems(): + for i in range(0, len(data_list)): + if key in data[i]: + data[i].append(value) + break return data @@ -68,18 +68,21 @@ def __group_annotations(self): Ideally, this method should only be called once. Use self.grouped_annotations after calling this method :return: """ - anno_list = self.annotations.values.tolist() - - anno = {} - for i in range(0, len(anno_list)): - value_id = anno_list[i][1] - annotation_code = anno_list[i][-1] - if value_id in anno: - anno[value_id].append(annotation_code) - else: - anno[value_id] = [annotation_code] - - return anno + if (self.annotations): + anno_list = self.annotations.values.tolist() + + anno = {} + for i in range(0, len(anno_list)): + value_id = anno_list[i][1] + annotation_code = anno_list[i][-1] + if value_id in anno: + anno[value_id].append(annotation_code) + else: + anno[value_id] = [annotation_code] + + return anno + else: + return None def EnableSorting(self): self.Bind(wx.EVT_LIST_COL_CLICK, self.on_column_selected) diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index ae5baed..4d8d58e 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -47,6 +47,7 @@ def __init__(self, taskserver=None): def reset_edit(self): sm = ServiceManager() self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + self.annotation_list = pd.DataFrame() setSchema(self.mem_service._session_factory.engine) def set_series_service(self, service): diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index c4d5375..4185fd3 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -512,58 +512,71 @@ def save(self, result=None): # upsert values self.memDB.series_service.upsert_values(values) # save new annotations - self.add_annotations(self.memDB.annotation_list) + if len(self.memDB.annotation_list >0): + self.add_annotations(self.memDB.annotation_list) + return result except Exception as e: logger.error("Exception encountered while saving: {}".format(e)) - return result + return None def save_existing(self, result): result = self.save(result) return result def save_appending(self, result, overwrite=True): - values = self.memDB.getDataValuesDF() - - # get value count - vc = result.ValueCount - # set in df - values["resultid"] = result.ResultID - - # count = overlap calc - count = self.overlapcalc(result, values, overwrite) - # set value count = res.vc+valuecount-count - valuecount = result.ValueCount + vc - count - # update result - self.updateResult(result, valuecount) - # insert values - self.memDB.series_service.upsert_values(values) - # save new annotations - self.add_annotations(self.memDB.annotation_list) - - return result - - def save_as(self, variable, method, proc_level, action, action_by): - #save as new series - values = self.memDB.getDataValuesDF() - # get all annotations for series - annolist= self.memDB.series_service.get_annotations_by_result(str(values["resultid"][0])) - annolist['valueid']=None + try: - # create series - result = self.getResult(variable, method, proc_level, action, action_by) + values = self.memDB.getDataValuesDF() - # set in df - values["resultid"] = result.ResultID - # insert values - self.memDB.series_service.insert_values(values) + # get value count + vc = result.ValueCount + # set in df + values["resultid"] = result.ResultID - # save all annotations - frames = [self.memDB.annotation_list, annolist] - annolist = pd.concat(frames) - self.add_annotations(annolist) + # count = overlap calc + count = self.overlapcalc(result, values, overwrite) + # set value count = res.vc+valuecount-count + valuecount = result.ValueCount + vc - count + # update result + self.updateResult(result, valuecount) + # insert values + self.memDB.series_service.upsert_values(values) + # save new annotations + if len(self.memDB.annotation_list >0): + self.add_annotations(self.memDB.annotation_list) + return result + except Exception as e: + logger.error("Exception encountered while performing a save as: {}".format(e)) + return None + def save_as(self, variable, method, proc_level, action, action_by): - return result + try: + #save as new series + values = self.memDB.getDataValuesDF() + # get all annotations for series + annolist= self.memDB.series_service.get_annotations_by_result(str(values["resultid"][0])) + annolist['valueid']=None + + # create series + result = self.getResult(variable, method, proc_level, action, action_by) + + # set in df + values["resultid"] = result.ResultID + # insert values + self.memDB.series_service.insert_values(values) + + #combine all of the annotations new annotations with the existing + frames = [self.memDB.annotation_list, annolist] + annolist = pd.concat(frames) + # save all annotations + if len(annolist >0): + self.add_annotations(annolist) + + return result + except Exception as e: + logger.error("Exception encountered while performing a save as: {}".format(e)) + return None def getResult(self, var, meth, proc, action, action_by): values = self.memDB.getDataValuesDF() @@ -602,17 +615,19 @@ def getResult(self, var, meth, proc, action, action_by): newaction.MethodID = action.MethodID newaction.ActionTypeCV = "Derivation" - print newaction + print "creating an action" newaction = self.memDB.series_service.create.createAction(newaction) # it times out. find out why print newaction # create Actionby done + print "creating an actionby" action_by.ActionID = newaction.ActionID action_by= self.memDB.series_service.create.createActionby(action_by) print action_by + print "creating a feature_action" # create FeatureAction (using current sampling feature id) sampling_feature = result.FeatureActionObj.SamplingFeatureObj self.memDB.series_service.read._session.expunge(result.FeatureActionObj.SamplingFeatureObj) @@ -625,6 +640,7 @@ def getResult(self, var, meth, proc, action, action_by): feature_action = self.memDB.series_service.create.createFeatureAction(feature_action) print feature_action + print "creating a result" # create TimeSeriesResult - this should also contain all of the stuff for the Result time, offset = self.get_current_time_and_utcoffset() @@ -674,9 +690,9 @@ def overlapcalc(self, result, values, overwrite): #is there any overlap dbend = result.FeatureActionObj.ActionObj.EndDateTime dfstart = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) - overlap = dbend>= dfstart + overlap = dbend >= dfstart #number of overlapping values - overlapdf = values[(values["valuedatetime"]<= dfstart) & (values["valuedatetime"]>= dbend)] + overlapdf = values[(values["valuedatetime"] <= dfstart) & (values["valuedatetime"] >= dbend)] count =len(overlapdf) #if not overwrite. remove any overlapping values from df if overlap: @@ -710,6 +726,7 @@ def add_annotations(self, annolist): mynewdf= newdf[["valueid_y","annotationid"]] mynewdf.columns = ["ValueID", "AnnotationID"] + # save df to db self.memDB.series_service.add_annotations(mynewdf) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index b808ff3..dea3402 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -611,8 +611,9 @@ def get_current_time_and_utcoffset(self): - def insert_annotations(self, annotations): - annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) + # def insert_annotations(self, annotations): + # setSchema(self._session_factory.engine) + # annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) def _get_df_query(self, values): @@ -787,6 +788,7 @@ def create_annotation(self, code, text, link=None): return self.create_annotation_by_anno(annotation) def add_annotations(self, anno_list): + setSchema(self._session_factory.engine) try: #tablename = TimeSeriesResultValueAnnotations.__tablename__ #print ("I am TS saving name the table name", tablename) @@ -872,6 +874,7 @@ def get_all_annotations(self): def get_annotations_by_result(self, resultid): resultid = int(resultid) + setSchema(self._session_factory.engine) q = self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime, Annotations.AnnotationCode)\ From f7c23861403548f84af4c3657c828fb14c75d4bf Mon Sep 17 00:00:00 2001 From: Mikaila Young Date: Wed, 25 Jan 2017 14:49:19 -0700 Subject: [PATCH 151/158] took out an unnecessary debug print statement --- odmtools/odmservices/edit_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index fa40508..40987d1 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -679,7 +679,6 @@ def overlapcalc(self, result, values, overwrite): def add_annotations(self, annolist): #match up with existing values and get value id - print("ANNOTATIONS ARE ATTEMPTED TO ADD") engine = self.memDB.series_service._session_factory.engine q =self.memDB.series_service._session.query(TimeSeriesResultValues) \ From 1e20fec44092d86e8a720846c35aa7d5caf5b54c Mon Sep 17 00:00:00 2001 From: stephanie Date: Wed, 25 Jan 2017 16:55:46 -0700 Subject: [PATCH 152/158] update saving style --- odmtools/odmservices/edit_service.py | 35 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 4185fd3..5062515 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -582,7 +582,7 @@ def getResult(self, var, meth, proc, action, action_by): values = self.memDB.getDataValuesDF() # copy old result = self.memDB.series_service.get_series(str(values["resultid"][0])) - newaction = Actions() + # change var, meth proc, in df #intend ts, agg sta @@ -594,9 +594,7 @@ def getResult(self, var, meth, proc, action, action_by): result.ProcessingLevelID = proc.ProcessingLevelID result.ProcessingLevelObj = proc - if meth: - newaction.MethodID = meth.MethodID - newaction.MethodObj = meth.MethodObj + #if result does not exist if not self.memDB.series_service.resultExists(result): @@ -606,25 +604,36 @@ def getResult(self, var, meth, proc, action, action_by): # self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) # self.memDB.series_service.read._session.expunge(action.MethodObj) + print "creating an action" + newaction = Actions() + if meth: + newaction.MethodID = meth.MethodID + #newaction.MethodObj = meth.MethodOb + else: + newaction.MethodID = action.MethodID newaction.ActionDescription = action.ActionDescription newaction.ActionFileLink = action.ActionFileLink newaction.BeginDateTime = action.BeginDateTime newaction.BeginDateTimeUTCOffset = action.BeginDateTimeUTCOffset newaction.EndDateTime = action.EndDateTime newaction.EndDateTimeUTCOffset = action.EndDateTimeUTCOffset - newaction.MethodID = action.MethodID newaction.ActionTypeCV = "Derivation" - print "creating an action" - newaction = self.memDB.series_service.create.createAction(newaction) # it times out. find out why + self.memDB.series_service._session.add(newaction) + self.memDB.series_service._session.commit() + #newaction = self.memDB.series_service.create.createAction(newaction) # it times out. find out why print newaction # create Actionby done print "creating an actionby" - action_by.ActionID = newaction.ActionID - action_by= self.memDB.series_service.create.createActionby(action_by) - print action_by + newaction_by= ActionBy() + newaction_by.ActionID = newaction.ActionID + newaction_by.AffiliationID = action_by.AffiliationID + + action_by= self.memDB.series_service._session.add(newaction_by) + self.memDB.series_service._session.commit() + print newaction_by print "creating a feature_action" @@ -637,7 +646,8 @@ def getResult(self, var, meth, proc, action, action_by): feature_action.ActionID = newaction.ActionID feature_action.ActionObj = newaction feature_action.SamplingFeatureObj = sampling_feature - feature_action = self.memDB.series_service.create.createFeatureAction(feature_action) + feature_action = self.memDB.series_service._session.add(feature_action) + self.memDB.series_service._session.commit() print feature_action print "creating a result" @@ -656,7 +666,8 @@ def getResult(self, var, meth, proc, action, action_by): self.memDB.series_service.read._session.expunge(result) - result = self.memDB.series_service.create.createResult(result) + result = self.memDB.series_service._session.add(result) + self.memDB.series_service._session.commit() print result except Exception as ex: self.memDB.series_service._session.rollback() From b43f6408fa22c40acb74beada0fc147e2d67c33b Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 6 Feb 2017 10:59:29 -0700 Subject: [PATCH 153/158] fix saving a new series --- odmtools/gui/wizSave.py | 180 ++++---- odmtools/odmdata/memory_database.py | 7 +- odmtools/odmservices/edit_service.py | 173 +++---- odmtools/odmservices/export_service.py | 6 +- odmtools/odmservices/series_service.py | 422 ++++++------------ odmtools/odmservices/service_manager.py | 9 + tests/test_odmservices/test_edit_service.py | 6 +- tests/test_odmservices/test_series_service.py | 18 +- 8 files changed, 310 insertions(+), 511 deletions(-) diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 102a81f..a057ad2 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -189,18 +189,19 @@ def on_wizard_finished(self, event): site, variable, method, action, proc_level = self.get_metadata() #if qcl exits use its its closeSuccessful = False - - rbSave = self.pgIntro.pnlIntroduction.rbSave.GetValue() - rbSaveAsNew = self.pgIntro.pnlIntroduction.rbSaveAs.GetValue() - rbSaveAsExisting = self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue() - if rbSaveAsExisting: - append = self.pgExisting.pnlExisting.rbAppend.GetValue() - overwrite = self.pgExisting.pnlExisting.rbOverwrite.GetValue() - if append: - original = self.pgExisting.pnlExisting.rbOriginal.GetValue() - new = self.pgExisting.pnlExisting.rbNew.GetValue() - - if proc_level.ProcessingLevelID == 0 and not rbSaveAsNew: + saveSuccessful=False + + self.rbSave = self.pgIntro.pnlIntroduction.rbSave.GetValue() + self.rbSaveAsNew = self.pgIntro.pnlIntroduction.rbSaveAs.GetValue() + self.rbSaveAsExisting = self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue() + if self.rbSaveAsExisting: + self.append = self.pgExisting.pnlExisting.rbAppend.GetValue() + self.overwrite = self.pgExisting.pnlExisting.rbOverwrite.GetValue() + if self.append: + self.original = self.pgExisting.pnlExisting.rbOriginal.GetValue() + self.new = self.pgExisting.pnlExisting.rbNew.GetValue() + + if proc_level.ProcessingLevelID == 0 and not self.rbSaveAsNew: """ If we're looking at a QCL with Control level 0 and the following cases: Save @@ -212,19 +213,19 @@ def on_wizard_finished(self, event): wx.YES_NO | wx.ICON_QUESTION) if val == 2: logger.info("User selected yes to save a level 0 dataset") - val_2 = wx.MessageBox("This interactive_item cannot be undone.\nAre you sure you are sure?\n", + val_2 = wx.MessageBox("This cannot be undone.\nAre you sure you are sure?\n", 'Are you REALLY sure?', wx.YES_NO | wx.ICON_QUESTION) if val_2 == 2: closeSuccessful = True - elif rbSaveAsExisting: + elif self.rbSaveAsExisting: keyword = "overwrite" if self.pgExisting.pnlExisting.rbAppend.GetValue(): keyword = "append to" - message = "You are about to " + keyword + " an existing series_service,\nthis interactive_item cannot be undone.\nWould you like to continue?\n" + message = "You are about to " + keyword + " an existing series_service,\nthis cannot be undone.\nWould you like to continue?\n" cont = wx.MessageBox(message, 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) if cont == 2: closeSuccessful = True @@ -234,95 +235,80 @@ def on_wizard_finished(self, event): closeSuccessful = True if closeSuccessful: - #if qcl exists use its id - # if self.series_service.qcl_exists(QCL): - # if QCL == self.currSeries.quality_control_level: - # QCL = None - # else: - # QCL = self.record_service.get_qcl(QCL) - # else: - # QCL = self.record_service.create_processing_level(QCL.code, QCL.definition, QCL.explanation) - if self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) is None: - proc_level = self.series_service.create_processing_level(proc_level.ProcessingLevelCode, proc_level.Definition, proc_level.Explanation) - elif proc_level.ProcessingLevelCode == self.__processing_level_from_series.ProcessingLevelCode: - proc_level = None - else: - proc_level = self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) - - - #if variable exists use its id - # if self.series_service.variable_exists(Variable): - # Variable = self.record_service.get_variable(Variable) - # else: - # Variable = self.record_service.create_variable(Variable) - if self.series_service.get_variable_by_code(variable.VariableCode) is None: - variable = self.series_service.create_variable_by_var(variable) - else: - variable = self.series_service.get_variable_by_code(variable.VariableCode) - - - #if method exists use its id - # if self.series_service.method_exists(Method): - # if Method == self.currSeries.method: - # Method = None - # else: - # Method = self.record_service.get_method(Method) - # else: - # Method = self.record_service.create_method(Method) - if self.series_service.get_method_by_code(method.MethodCode) is None: - method = self.series_service.create_method(method.MethodDescription, method.MethodLink) - elif method == self.__method_from_series: - method = None - else: - method = self.series_service.get_method_by_code(method.MethodCode) - - # initiate either "Save as" or "Save" - ''' - if self.page1.pnlIntroduction.rbSave.GetValue(): - result = self.record_service.save(Variable, Method, QCL, False) - else: - result = self.record_service.saveAs(Variable, Method, QCL, True) - ''' - affiliation = self.action_page.get_affiliation() - - action_by = ActionBy() - #action_by.ActionID = action.ActionID - action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() - action_by.AffiliationID = affiliation.AffiliationID - action_by.AffiliationObj = affiliation - - # result = self.series_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) - result = self.pgExisting.pnlExisting.olvSeriesList.GetSelectedObject().ResultObj - - #result = self.record_service._edit_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) - try: - if rbSave: - result = self.record_service.save() - elif rbSaveAsNew: - result = self.record_service.save_as(variable=variable, method=method, proc_level=proc_level, - action=action, action_by=action_by) - elif rbSaveAsExisting: - if overwrite: - result = self.record_service.save_existing(result=result) - elif append: - #TODO send in just the result - #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): - #TODO if i require that original or new is selected I can call once with overwrite = original - if original: - result = self.record_service.save_appending(result=result, overwrite=False) - elif new: - result = self.record_service.save_appending(result=result, overwrite=True) - - Publisher.sendMessage("refreshSeries") - - #self.page1.pnlIntroduction.rb + saveSuccessful = self.try_to_save(variable, method, proc_level, action) except Exception as e: message = "Save was unsuccessful %s" % e.message logger.error(message) wx.MessageBox(message, "Error!", wx.ICON_ERROR | wx.ICON_EXCLAMATION) + saveSuccessful=False + + if saveSuccessful: event.Skip() self.Close() + self.Destroy() + + def create_needed_meta(self, proc_level,variable, method): + if self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) is None: + proc_level = self.series_service.create_processing_level(proc_level.ProcessingLevelCode, proc_level.Definition, proc_level.Explanation) + elif proc_level.ProcessingLevelCode == self.__processing_level_from_series.ProcessingLevelCode: + proc_level = None + else: + proc_level = self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) + + + + if self.series_service.get_variable_by_code(variable.VariableCode) is None: + variable = self.series_service.create_variable_by_var(variable) + else: + variable = self.series_service.get_variable_by_code(variable.VariableCode) + + + + if self.series_service.get_method_by_code(method.MethodCode) is None: + method = self.series_service.create_method(method.MethodDescription, method.MethodLink) + elif method == self.__method_from_series: + method = None + else: + method = self.series_service.get_method_by_code(method.MethodCode) + + + def try_to_save(self, variable, method, proc_level, action): + self.create_needed_meta(proc_level, variable, method) + affiliation = self.action_page.get_affiliation() + + action_by = ActionBy() + # action_by.ActionID = action.ActionID + action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() + action_by.AffiliationID = affiliation.AffiliationID + action_by.AffiliationObj = affiliation + + # result = self.series_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) + result = self.pgExisting.pnlExisting.olvSeriesList.GetSelectedObject().ResultObj + + if self.rbSave: + result = self.record_service.save() + elif self.rbSaveAsNew: + result = self.record_service.save_as(variable=variable, method=method, proc_level=proc_level, + action=action, action_by=action_by) + elif self.rbSaveAsExisting: + if self.overwrite: + result = self.record_service.save_existing(result=result) + elif self.append: + #TODO send in just the result + #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): + #TODO if i require that original or new is selected I can call once with overwrite = original + if self.original: + result = self.record_service.save_appending(result=result, overwrite=False) + elif self.new: + result = self.record_service.save_appending(result=result, overwrite=True) + + Publisher.sendMessage("refreshSeries") + return True + + + + diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 4d8d58e..e5d8c14 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -3,7 +3,6 @@ from sqlalchemy import bindparam from odmtools.common.logger import LoggerTool -from odmtools.odmservices import SeriesService from odmtools.odmservices import ServiceManager, SeriesService # from odmtools.odmdata import SeriesService#ODM @@ -12,11 +11,10 @@ from odm2api.ODM2.models import setSchema import pandas as pd - logger =logging.getLogger('main') class MemoryDatabase(object): - ### this code should be changed to work with the database abstract layer so that sql queries are not in the code + ## this code should be changed to work with the database abstract layer so that sql queries are not in the code # series_service is a SeriesService def __init__(self, taskserver=None): @@ -75,7 +73,6 @@ def getDataValuesDF(self): logging.debug("done updating memory dataframe") return self.df - def get_annotations(self, query_db_again=False): # self.mem_service._session.commit() setSchema(self.series_service._session_factory.engine) @@ -84,8 +81,6 @@ def get_annotations(self, query_db_again=False): annotation = self.series_service.get_annotations_by_result(resultid=result_id) self.results_annotations = annotation - - def getDataValues(self): # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore self.mem_service._session.commit() diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 5062515..8c27851 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -517,6 +517,7 @@ def save(self, result=None): return result except Exception as e: logger.error("Exception encountered while saving: {}".format(e)) + raise e return None def save_existing(self, result): @@ -547,6 +548,7 @@ def save_appending(self, result, overwrite=True): return result except Exception as e: logger.error("Exception encountered while performing a save as: {}".format(e)) + raise e return None def save_as(self, variable, method, proc_level, action, action_by): @@ -556,12 +558,13 @@ def save_as(self, variable, method, proc_level, action, action_by): values = self.memDB.getDataValuesDF() # get all annotations for series annolist= self.memDB.series_service.get_annotations_by_result(str(values["resultid"][0])) - annolist['valueid']=None + annolist['valueid'] = None # create series result = self.getResult(variable, method, proc_level, action, action_by) # set in df + values["valueid"] = None values["resultid"] = result.ResultID # insert values self.memDB.series_service.insert_values(values) @@ -570,30 +573,43 @@ def save_as(self, variable, method, proc_level, action, action_by): frames = [self.memDB.annotation_list, annolist] annolist = pd.concat(frames) # save all annotations - if len(annolist >0): + if len(annolist > 0): self.add_annotations(annolist) return result except Exception as e: logger.error("Exception encountered while performing a save as: {}".format(e)) - return None + raise e + def getResult(self, var, meth, proc, action, action_by): - values = self.memDB.getDataValuesDF() + id = self.memDB.getDataValuesDF()["resultid"] + # copy old - result = self.memDB.series_service.get_series(str(values["resultid"][0])) + # what is my original result + result = self.memDB.series_service.get_series(str(id[0])) + sfid = result.FeatureActionObj.SamplingFeatureID + aggcv = result.AggregationStatisticCV + itsp = result.IntendedTimeSpacing + itspunit = result.IntendedTimeSpacingUnitsID + status = result.StatusCV + type = result.ResultTypeCV + units = result.UnitsID + medium = result.SampledMediumCV + self.memDB.series_service._session.expunge(result) # change var, meth proc, in df #intend ts, agg sta if var: result.VariableID = var.VariableID - result.VariableObj = var + if proc: result.ProcessingLevelID = proc.ProcessingLevelID - result.ProcessingLevelObj = proc + result.ResultID=None + result.ResultUUID = None #if result does not exist @@ -601,111 +617,77 @@ def getResult(self, var, meth, proc, action, action_by): try: #create Action - # self.memDB.series_service.read._session.expunge(action.MethodObj.OrganizationObj) - # self.memDB.series_service.read._session.expunge(action.MethodObj) - - print "creating an action" - newaction = Actions() if meth: - newaction.MethodID = meth.MethodID - #newaction.MethodObj = meth.MethodOb + id = meth.MethodID + # new_action.MethodObj = meth.MethodOb else: - newaction.MethodID = action.MethodID - newaction.ActionDescription = action.ActionDescription - newaction.ActionFileLink = action.ActionFileLink - newaction.BeginDateTime = action.BeginDateTime - newaction.BeginDateTimeUTCOffset = action.BeginDateTimeUTCOffset - newaction.EndDateTime = action.EndDateTime - newaction.EndDateTimeUTCOffset = action.EndDateTimeUTCOffset - newaction.ActionTypeCV = "Derivation" - - self.memDB.series_service._session.add(newaction) - self.memDB.series_service._session.commit() - #newaction = self.memDB.series_service.create.createAction(newaction) # it times out. find out why - print newaction - - - # create Actionby done - print "creating an actionby" - newaction_by= ActionBy() - newaction_by.ActionID = newaction.ActionID - newaction_by.AffiliationID = action_by.AffiliationID - - action_by= self.memDB.series_service._session.add(newaction_by) - self.memDB.series_service._session.commit() - print newaction_by - - - print "creating a feature_action" + id = action.MethodID + new_action, action_by = self.memDB.series_service.create_action(id, action.ActionDescription, action.ActionFileLink, action.BeginDateTime, action.BeginDateTimeUTCOffset, action_by) + # create FeatureAction (using current sampling feature id) - sampling_feature = result.FeatureActionObj.SamplingFeatureObj - self.memDB.series_service.read._session.expunge(result.FeatureActionObj.SamplingFeatureObj) - - feature_action = FeatureActions() - feature_action.SamplingFeatureID = sampling_feature.SamplingFeatureID - feature_action.ActionID = newaction.ActionID - feature_action.ActionObj = newaction - feature_action.SamplingFeatureObj = sampling_feature - feature_action = self.memDB.series_service._session.add(feature_action) - self.memDB.series_service._session.commit() - print feature_action - - print "creating a result" - # create TimeSeriesResult - this should also contain all of the stuff for the Result - time, offset = self.get_current_time_and_utcoffset() - - result.ResultID = None - result.ResultUUID = None - result.ValueCount = 0 - result.FeatureActionID = feature_action.FeatureActionID - result.ResultDateTime = time - result.ResultDateTimeUTCOffset = offset - result.FeatureActionObj= feature_action - self.memDB.series_service.read._session.expunge(result.ProcessingLevelObj) - self.memDB.series_service.read._session.expunge(result.VariableObj) - self.memDB.series_service.read._session.expunge(result) - - - result = self.memDB.series_service._session.add(result) - self.memDB.series_service._session.commit() - print result + feature_action = self.memDB.series_service.createFeatureAction(sfid, new_action.ActionID) + + if var: + varid = var.VariableID + else: + varid = result.VariableID + + if proc: + procid= proc.ProcessingLevelID + else: + procid= result.ProcessingLevelID + result = self.memDB.series_service.create_result(varid, procid, feature_action.FeatureActionID, + aggcv, itsp, itspunit, status, type, units, medium) + except Exception as ex: self.memDB.series_service._session.rollback() print ex - return self.updateResult(result) + raise ex + else: + #if saveas called me throw an error that this series already exists + import inspect + (frame, filename, line_number, + function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[1] + + if function_name =='save_as': + raise Exception("this series already exists, but you have chosen to create a new series") + else: + #it already exists, so get it + result = self.memDB.series_service.get_series_by_meta(result) + return self.updateResult(result) - def updateResult(self, result, valuecount = -10): + def updateResult(self, result, valuecount=-10): form = "%Y-%m-%d %H:%M:%S" # get pd values = self.memDB.getDataValuesDF() # update count, dates, action = result.FeatureActionObj.ActionObj - action.BeginDateTime= datetime.datetime.strptime(str(np.min(values['valuedatetime'])), form) + action.BeginDateTime = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) - if valuecount > 0 : - result.ValueCount=valuecount + + #TODO how does valuecount change, when do i send it in + if valuecount > 0: + result.ValueCount = valuecount else: result.ValueCount = len(values) - setSchema(self.memDB.series_service._session_factory.engine) - self.memDB.series_service.update.updateResult(result.ResultID, result.ValueCount) - self.memDB.series_service.update.updateAction(actionID=action.ActionID, - begin=action.BeginDateTime, end=action.EndDateTime) + self.memDB.series_service.update_result(result=result) + self.memDB.series_service.update_action(action=action) return result def overlapcalc(self, result, values, overwrite): form = "%Y-%m-%d %H:%M:%S" - #is there any overlap + # is there any overlap dbend = result.FeatureActionObj.ActionObj.EndDateTime dfstart = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) overlap = dbend >= dfstart - #number of overlapping values + # number of overlapping values overlapdf = values[(values["valuedatetime"] <= dfstart) & (values["valuedatetime"] >= dbend)] - count =len(overlapdf) - #if not overwrite. remove any overlapping values from df + count = len(overlapdf) + # if not overwrite. remove any overlapping values from df if overlap: if not overwrite: values = values[values["valuedatetime"] > dbend] @@ -714,9 +696,9 @@ def overlapcalc(self, result, values, overwrite): return count def add_annotations(self, annolist): - #match up with existing values and get value id + # match up with existing values and get value id - print("ANNOTATIONS ARE ATTEMPTED TO ADD") + print("Adding Annotations") engine = self.memDB.series_service._session_factory.engine q =self.memDB.series_service._session.query(TimeSeriesResultValues) \ @@ -727,13 +709,12 @@ def add_annotations(self, annolist): # params=query.params) # query = "SELECT ValueID, ResultID, ValueDateTime FROM TimeSeriesResultValues Where ResultID="+annolist["ResultID"][0] - vals = pd.read_sql_query(sql=query, con=engine,params=query.params) + vals = pd.read_sql_query(sql=query, con=engine, params=query.params) # remove any duplicates - annolist.drop_duplicates(["resultid", "annotationid", "valuedatetime"], keep= 'last', inplace = True) - newdf = pd.merge(annolist, vals, how='left', on= ["resultid", "valuedatetime"], indicator = True) - + annolist.drop_duplicates(["resultid", "annotationid", "valuedatetime"], keep='last', inplace=True) + newdf = pd.merge(annolist, vals, how='left', on=["resultid", "valuedatetime"], indicator=True) - #get only AnnotationID and ValueID + # get only AnnotationID and ValueID mynewdf= newdf[["valueid_y","annotationid"]] mynewdf.columns = ["ValueID", "AnnotationID"] @@ -932,11 +913,3 @@ def reconcile_dates(self, parent_series_id): pass - def get_current_time_and_utcoffset(self): - current_time = datetime.datetime.now() - utc_time = datetime.datetime.utcnow() - - difference_in_timezone = current_time - utc_time - offset_in_hours = difference_in_timezone.total_seconds() / 3600 - - return current_time, offset_in_hours \ No newline at end of file diff --git a/odmtools/odmservices/export_service.py b/odmtools/odmservices/export_service.py index 4b6d1eb..9cbd7a5 100644 --- a/odmtools/odmservices/export_service.py +++ b/odmtools/odmservices/export_service.py @@ -14,7 +14,7 @@ def __init__(self, series_service): def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, src=False, qcl=False): - series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) if series is None: return False @@ -144,11 +144,11 @@ def export_series_metadata(self, series_ids, filename): pass if isinstance(series_ids, int): - series = self._series_service.get_series_by_id(series_ids) + series = self._series_service.get_series(series_ids) self.append_series_node(series, list_root) else: for series_id in series_ids: - series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) self.append_series_node(series, list_root) tree = ET.ElementTree(root) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index dea3402..c7c19d7 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -3,11 +3,9 @@ from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 from odm2api import serviceBase from odm2api.ODM2.models import * -#from odmtools.odmservices.to_sql_newrows import get_insert, get_delete, get_update import datetime -from odmtools.common.logger import LoggerTool import pandas as pd -logger =logging.getLogger('main') +logger = logging.getLogger('main') class SeriesService(serviceBase): @@ -19,10 +17,9 @@ def __init__(self, connection, debug=False): self.update = UpdateODM2(self._session_factory) self.delete = DeleteODM2(self._session_factory) self.create = CreateODM2(self._session_factory) - #send in engine + # send in engine setSchema(self._session_factory.engine) - def reset_session(self): self.read.reset_session() self.update.reset_session() @@ -45,9 +42,10 @@ def get_used_sites(self): except: return None - sf=[x[0] for x in self._session.query(distinct(FeatureActions.SamplingFeatureID)).filter(FeatureActions.FeatureActionID.in_(fas)).all()] + sf = [x[0] for x in self._session.query(distinct(FeatureActions.SamplingFeatureID)) + .filter(FeatureActions.FeatureActionID.in_(fas)).all()] - sites = self.read.getSamplingFeatures(type = "site", ids = sf) + sites = self.read.getSamplingFeatures(type="site", ids=sf) return sites def get_used_variables(self): @@ -63,8 +61,6 @@ def get_used_variables(self): vars= self.read.getVariables(ids = ids) return vars - - # Query DetailedResultInfo/series object is for Display purposes def get_all_series(self, siteid = None): """ @@ -74,7 +70,6 @@ def get_all_series(self, siteid = None): setSchema(self._session_factory.engine) - return self.read.getDetailedResultInfo('Time Series Coverage', sfID=siteid) def get_series(self, series_id=None): @@ -117,7 +112,7 @@ def get_variables_by_site_code(self, site_code): return q.all() # Series Catalog methods - def get_series_by_site(self , site_id): + def get_series_by_site(self, site_id): # try: # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() # return selectedSeries @@ -132,17 +127,14 @@ def get_series_by_site(self , site_id): # return self.read.getResults(type="site", ids= [site_id])[0] return self.read.getResults(ids=[site_id]) - - # Site methods def get_all_sites(self): """ :return: List[Sites] """ - #return self._edit_session.query(Site).order_by(Site.code).all() + # return self._edit_session.query(Site).order_by(Site.code).all() return self.read.getResults(type="site") - def get_site_by_id(self, site_id): """ return a Site object that has an id=site_id @@ -154,14 +146,13 @@ def get_site_by_id(self, site_id): # except: # return None - return self.read.getSampling(ids = [site_id])[0] - + return self.read.getSampling(ids=[site_id])[0] def get_all_variables(self): """ :return: List[Variables] """ - #return self._edit_session.query(Variable).all() + # return self._edit_session.query(Variable).all() return self.read.getVariables() def get_variable_by_id(self, variable_id): @@ -173,8 +164,8 @@ def get_variable_by_id(self, variable_id): # return self._edit_session.query(Variable).filter_by(id=variable_id).first() # except: # return None - return self.read.getVariables(ids = [variable_id])[0] -# + return self.read.getVariables(ids=[variable_id])[0] + def get_variable_by_code(self, variable_code): """ @@ -185,10 +176,8 @@ def get_variable_by_code(self, variable_code): # return self._edit_session.query(Variable).filter_by(code=variable_code).first() # except: # return None - return self.read.getVariables(codes = [variable_code])[0] -# + return self.read.getVariables(codes=[variable_code])[0] -# # Unit methods def get_all_units(self): """ @@ -197,7 +186,7 @@ def get_all_units(self): """ # return self._edit_session.query(Unit).all() return self.read.getUnits() -# + def get_unit_by_name(self, unit_name): """ :param unit_name: str @@ -208,7 +197,7 @@ def get_unit_by_name(self, unit_name): # except: # return None return self.read.getUnits(name=[unit_name])[0] -# + def get_unit_by_id(self, unit_id): """ @@ -221,7 +210,6 @@ def get_unit_by_id(self, unit_id): # return None return self.read.getUnits(ids=[unit_id])[0] -# def get_all_qualifiers(self): """ @@ -231,7 +219,7 @@ def get_all_qualifiers(self): # return result ann= self.read.getAnnotations() return ann -# + def get_qualifier_by_code(self, code): """ @@ -239,10 +227,10 @@ def get_qualifier_by_code(self, code): # """ # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() # return result - return self.read.getAnnotations(codes=[code])[0] ##todo: CHECK ON THIS -# + return self.read.getAnnotations(codes=[code])[0] # todo: CHECK ON THIS + def get_qualifiers_by_series_id(self, series_id): - return self.read.getAnnotations(ids=[series_id])[0] ##todo: check on this + return self.read.getAnnotations(ids=[series_id])[0] # todo: check on this def get_all_processing_levels(self): return self.read.getProcessingLevels(ids=None, codes=None) @@ -257,25 +245,24 @@ def get_all_processing_levels(self): # return self._edit_session.query(Qualifier).join(subquery).distinct().all() # - def get_processing_level_by_id(self, qcl_id): try: - return self.read.getProcessingLevels(ids = [qcl_id])[0] - #return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() + return self.read.getProcessingLevels(ids=[qcl_id])[0] + # return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() except: return None -# + def get_processing_level_by_code(self, codes): try: return self.read.getProcessingLevels(codes=[codes])[0] except: return None -# # Method methods + # Method methods def get_all_methods(self): - #return self._edit_session.query(Method).all() + # return self._edit_session.query(Method).all() return self.read.getMethods() -# + def get_method_by_id(self, method_id): return self.read.getMethods(ids=[method_id])[0] # try: @@ -283,7 +270,7 @@ def get_method_by_id(self, method_id): # except: # result = None # return result -# + def get_method_by_code(self, method_code): try: return self.read.getMethods(codes=[method_code])[0] @@ -296,58 +283,58 @@ def get_method_by_code(self, method_code): # result = None # logger.error("method not found") # return result -# - -#todo: Take another look at this + # todo: Take another look at this # Series Catalog methods def resultExists(self, result): """ - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: + :param result :return: Series """ # unique Result # FeatureActionID, ResultTypeCV, VariableID, UnitsID, ProcessingLevelID, SampledMediumCV - try: # return self._edit_session.query(Results).filter_by( # VariableID=var_id, MethodID=method_id, # AnnotationID=qcl_id).first() - ret = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV). - where(Results.VariableID == result.VariableID). - where(Results.UnitsID == result.UnitsID). - where(Results.ProcessingLevelID == result.ProcessingLevelID). - where(Results.SampledMediumCV == result.SampledMediumCV) + setSchema(self._session_factory.engine) + ret = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV) + .where(Results.VariableID == result.VariableID) + .where(Results.UnitsID == result.UnitsID) + .where(Results.ProcessingLevelID == result.ProcessingLevelID) + .where(Results.SampledMediumCV == result.SampledMediumCV) ) - # where(Results.FeatureActionID == result.FeatureActionID). - - + # where(Results.FeatureActionID == result.FeatureActionID). return ret.scalar() + except: return None - + def get_series_by_meta(self, result): + setSchema(self._session_factory.engine) + id = self.read._session.query(Results)\ + .filter_by(ResultTypeCV=result.ResultTypeCV)\ + .filter_by(VariableID=result.VariableID)\ + .filter_by(UnitsID=result.UnitsID)\ + .filter_by(ProcessingLevelID=result.ProcessingLevelID)\ + .filter_by(SampledMediumCV=result.SampledMediumCV) + return id.first() def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass - - #Data Value Methods + # Data Value Methods def get_values(self, series_id=None): """ :param series_id: :return: pandas dataframe """ # see get_annotations_by_result around line 850 - + setSchema(self._session_factory.engine) q = self.read._session.query(TimeSeriesResultValues) if series_id: q = q.filter_by(ResultID=series_id) @@ -358,26 +345,8 @@ def get_values(self, series_id=None): params=query.params) data.set_index(data['valuedatetime'], inplace=True) - # if series_id: - # anno = self.get_annotations_by_result(series_id) - # q = pd.merge(data, anno, how="left", on='valueid', indicator=False) - # data.applymap(self.merge_annotation_with_timeseries_result) - - # if len(anno): - # # data.valueid.apply(self.merge_annotation_with_timeseries_result) - # # data.applymap(self.merge_annotation_with_timeseries_result) - # self.merge_annotation_with_timeseries_result(data, anno) - - - return data - # df.drop_duplicates(dup_cols, keep='last', inplace=True) - # newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) - # newdf = newdf[newdf['_merge'] == 'left_only'] - # newdf.drop(['_merge'], axis=1, inplace=True) - # return df[df['valuedatetime'].isin(newdf['valuedatetime'])] - def get_all_values_df(self): """ @@ -387,34 +356,21 @@ def get_all_values_df(self): q = self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) - #columns = list(data) - - # columns.insert(0, columns.pop(columns.index("DataValue"))) - # columns.insert(1, columns.pop(columns.index("ValueDateTime"))) - #columns.insert(2, columns.pop(columns.index("QualifierID"))) + params=query.params) - #data = data.ix[:, columns] return data.set_index(data['ValueDateTime']) - # q = self._edit_session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) - # query = q.statement.compile(dialect = self._session_factory.engine.dialect) - # data = pd.read_sql_query(sql= query, - # con= self._session_factory.engine, - # params=query.params) -# - def get_all_values_list(self): """ :return: """ - result =self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() + result = self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() return [x.list_repr() for x in result] def get_all_values(self): return self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() -# + @staticmethod def calcSeason(row): @@ -447,103 +403,96 @@ def get_plot_values(self, seriesID, noDataValue, startDate=None, endDate=None): data["month"] = data['valuedatetime'].apply(lambda x: x.month) data["year"] = data['valuedatetime'].apply(lambda x: x.year) data["season"] = data.apply(self.calcSeason, axis=1) - # data.set_index(data['valuedatetime'], inplace=True) return data def get_all_plot_values(self): setSchema(self._session_factory.engine) Values = self.get_values() data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] - # data = data[data['datavalue'] != noDataValue] - data["month"] = data['valuedatetime'].apply(lambda x: x.month) data["year"] = data['valuedatetime'].apply(lambda x: x.year) data["season"] = data.apply(self.calcSeason, axis=1) - #data.set_index(data['valuedatetime'], inplace=True) return data - - - - -# def get_data_value_by_id(self, id): -# """ -# -# :param id: -# :return: -# """ -# try: -# return self._edit_session.query(DataValue).filter_by(id=id).first() -# except: -# return None -# -# -# - ##################### # # Update functions # ##################### -# def update_series(self, series): -# """ -# -# :param series: -# :return: -# """ -# merged_series = self._edit_session.merge(series) -# self._edit_session.add(merged_series) -# self._edit_session.commit() -# -# def update_dvs(self, dv_list): -# """ -# -# :param dv_list: -# :return: -# """ -# merged_dv_list = map(self._edit_session.merge, dv_list) -# self._edit_session.add_all(merged_dv_list) -# self._edit_session.commit() -# + def update_result(self, result): + # self.update.updateResult(result.ResultID, result.ValueCount) + self.update.updateResult(result=result) + + + def update_action(self, action): + self.update.updateAction(action=action) ##################### # # Create functions # ##################### + # new series + def create_result(self, var, proc, feature_action, aggcv, itsp, itspunit, status, type, units, medium): + + new_result = TimeSeriesResults() + time, offset = self.get_current_time_and_utcoffset() + new_result.ResultDateTime = time + new_result.ResultDateTimeUTCOffset = offset + # create TimeSeriesResult - this should also contain all of the stuff for the Result + new_result.ValueCount = 0 + new_result.FeatureActionID = feature_action + new_result.ResultDateTime = time + new_result.ResultDateTimeUTCOffset = offset + new_result.VariableID = var + new_result.ProcessingLevelID = proc + new_result.AggregationStatisticCV = aggcv + new_result.IntendedTimeSpacingUnitsID = itspunit + new_result.IntendedTimeSpacing = itsp + new_result.StatusCV = status + new_result.ResultTypeCV = type + new_result.UnitsID = units + new_result.SampledMediumCV = medium -#new series - def createResult(self, var, meth, proc): - #also create an action - #copy old - #change var, meth proc, in df #intend ts, agg stat - # Result = None - # result = Results() - result = Results() - if isinstance(var, Variables): - result.VariableID = var.VariableID - result.VariableObj = var + self.create.createResult(result=new_result) + self._session.refresh(new_result) + return new_result - if isinstance(meth, Methods): - # do something with meth - pass - if isinstance(proc, ProcessingLevels): - result.ProcessingLevelID = proc.ProcessingLevelID - result.ProcessingLevelObj = proc + def create_action(self, methodid, description, filelink, begindate, utc, actionby): + new_action = Actions() + new_action.MethodID= methodid + new_action.ActionDescription = description + new_action.ActionFileLink = filelink + new_action.BeginDateTime = begindate + new_action.BeginDateTimeUTCOffset = utc + new_action.EndDateTime = None + new_action.EndDateTimeUTCOffset = None + new_action.ActionTypeCV = "Derivation" - time, offset = self.get_current_time_and_utcoffset() - result.ResultDateTime = time - result.ResultDateTimeUTCOffset = offset + self.create.createAction(new_action) + action_by = new_action + action_by.ActionID = new_action.ActionID + action_by.IsActionLead = True + + self.create.createActionby(action_by) + + return new_action, actionby + + def createFeatureAction(self, sfid, actionid): + feature_action = FeatureActions() - return self.create.createResult(result=result) + feature_action.SamplingFeatureID = sfid + feature_action.ActionID = actionid + self.create.createFeatureAction(feature_action) + return feature_action def get_current_time_and_utcoffset(self): current_time = datetime.datetime.now() @@ -554,98 +503,26 @@ def get_current_time_and_utcoffset(self): return current_time, offset_in_hours - - - - -# def save_series(self, series, dvs): -# """ Save to an Existing Series -# :param series: -# :param data_values: -# :return: -# """ -# -# if self.series_exists(series): -# -# try: -# self._edit_session.add(series) -# self._edit_session.commit() -# self.save_values(dvs) -# except Exception as e: -# self._edit_session.rollback() -# raise e -# logger.info("Existing File was overwritten with new information") -# return True -# else: -# logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") -# # there wasn't an existing file to overwrite -# raise Exception("Series does not exist, unable to save. Please select 'Save As'") -# -# - # def save_new_series(self, series, dvs): - # """ Create as a new catalog entry - # :param series: - # :param data_values: - # :return: - # """ - # # Save As case - # if self.series_exists(series): - # msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - # logger.info(msg) - # raise Exception(msg) - # else: - # try: - # self._edit_session.add(series) - # self._edit_session.commit() - # self.save_values(dvs) - # #self._edit_session.add_all(dvs) - # except Exception as e: - # self._edit_session.rollback() - # raise e - # - # logger.info("A new series was added to the database, series id: "+str(series.id)) - # return True - - - - - - - # def insert_annotations(self, annotations): - # setSchema(self._session_factory.engine) - # annotations.to_sql(name="timeseriesresultvalueannotations", if_exists='append', con=self._session_factory.engine, index=False) - - - def _get_df_query(self, values): - - resid = str(values['resultid'][0]) - sd = values['valuedatetime'].min() - ed = values['valuedatetime'].max() - q = self.read._session.query(TimeSeriesResultValues).\ - filter(TimeSeriesResultValues.ResultID == resid)#.\ - #filter(TimeSeriesResultValues.ValueDateTime.between(sd, ed)) - return q.statement.compile(dialect=self._session_factory.engine.dialect) - - def upsert_values(self, values): setSchema(self._session_factory.engine) query = self._get_df_query(values) - newvals= get_insert(df= values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + newvals= self.get_insert(df=values, query=query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) if not newvals.empty: self.insert_values(newvals) - delvals = get_delete(df= values, query = query, dup_cols = ["valuedatetime", "resultid"], engine = self._session_factory.engine) + delvals = self.get_delete(df= values, query = query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) if not delvals.empty: self.delete_dvs(delvals["valuedatetime"].tolist()) - upvals = get_update(df= values, query= query, dup_cols = ["valuedatetime", "resultid"], engine= self._session_factory.engine) + upvals = self.get_update(df=values, query=query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) if not upvals.empty: self.update_values(upvals) - self._session.commit() def insert_values(self, values): """ - :param values: pandas dataframe :return: """ @@ -671,52 +548,13 @@ def update_values(self, updates): # update_list = {'value':updates["datavalue"].tolist(), 'id':updates.index.to_pydatetime().tolist()} vals = self.create._session.execute(stmt, update_list) - -# def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): -# """ -# -# :param data_values: -# :param site_id: -# :param variable_id: -# :param method_id: -# :param source_id: -# :param qcl_id: -# :return: -# """ -# self.update_dvs(data_values) -# series = Series() -# series.site_id = site_id -# series.variable_id = variable_id -# series.method_id = method_id -# series.source_id = source_id -# series.quality_control_level_id = qcl_id -# -# self._edit_session.add(series) -# self._edit_session.commit() -# return series - - - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - # ToDo: create a Result, TimeSeriesResult and an Action object of type derivation - """ - series_service -> Result in ODM2 - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Results() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - - return self.create_service.getResult(series) + def _get_df_query(self, values): + resid = str(values['resultid'][0]) + startdate = values['valuedatetime'].min() + ed = values['valuedatetime'].max() + q = self.read._session.query(TimeSeriesResultValues)\ + .filter(TimeSeriesResultValues.ResultID == resid) + return q.statement.compile(dialect=self._session_factory.engine.dialect) def create_method(self, description, link): """ @@ -772,6 +610,7 @@ def create_annotation(self, code, text, link=None): """ :param code: :param text: + :param link: :return: """ annotation = Annotations() @@ -790,8 +629,7 @@ def create_annotation(self, code, text, link=None): def add_annotations(self, anno_list): setSchema(self._session_factory.engine) try: - #tablename = TimeSeriesResultValueAnnotations.__tablename__ - #print ("I am TS saving name the table name", tablename) + anno_list.to_sql(name="TimeSeriesResultValueAnnotations", schema=TimeSeriesResultValueAnnotations.__table_args__['schema'], if_exists='append', @@ -877,14 +715,14 @@ def get_annotations_by_result(self, resultid): setSchema(self._session_factory.engine) q = self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, - TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime, Annotations.AnnotationCode)\ + TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime, Annotations.AnnotationCode)\ .filter(TimeSeriesResultValues.ResultID == resultid)\ .filter(TimeSeriesResultValueAnnotations.ValueID == TimeSeriesResultValues.ValueID)\ .filter(Annotations.AnnotationID==TimeSeriesResultValueAnnotations.AnnotationID) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) + params=query.params) return data def get_aggregation_statistic(self): @@ -975,25 +813,24 @@ def get_values_by_series(self, series_id): return q.all() + def get_delete(self, df, engine, query, dup_cols=[]): - def get_delete(df, engine, query, dup_cols=[]): - #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) df.drop_duplicates(dup_cols, keep='last', inplace=True) newdf = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) newdf = newdf[newdf['_merge'] == 'right_only'] newdf.drop(['_merge'], axis=1, inplace=True) return df[df['valuedatetime'].isin(newdf['valuedatetime'])] - def get_update(df, engine, query, dup_cols=[]): - #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + def get_update(self, df, engine, query, dup_cols=[]): + df.drop_duplicates(dup_cols, keep='last', inplace=True) newdf = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) - #newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) test = newdf[newdf['datavalue_x'] != newdf['datavalue_y']] return df[df['valuedatetime'].isin(test['valuedatetime'])] - def get_insert(df, engine, query, dup_cols=[]): + def get_insert(self, df, engine, query, dup_cols=[]): """ Remove rows from a dataframe that already exist in a database Required: @@ -1010,10 +847,9 @@ def get_insert(df, engine, query, dup_cols=[]): Returns Unique list of values from dataframe compared to database table """ - - #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) df.drop_duplicates(dup_cols, keep='last', inplace=True) newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) newdf = newdf[newdf['_merge'] == 'left_only'] newdf.drop(['_merge'], axis=1, inplace=True) - return df[df['valuedatetime'].isin(newdf['valuedatetime'])] \ No newline at end of file + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 94f331d..d32b767 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -29,6 +29,7 @@ def __init__(self, debug=False, conn_dict=None): self.debug = debug f = self._get_file('r') self._conn_dicts = [] + #self.version = 0 self._connection_format = "%s+%s://%s:%s@%s/%s" @@ -129,6 +130,14 @@ def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] def get_series_service(self, conn_dict=None, conn_string=""): + #TODO check connection what if they are changing + + if 'series_service' in locals():# or self.series_service is None): + return self.series_service + else: + return self._create_series_service( conn_dict, conn_string) + + def _create_series_service(self, conn_dict=None, conn_string=""): if not conn_dict: conn_dict = self.get_current_conn_dict() diff --git a/tests/test_odmservices/test_edit_service.py b/tests/test_odmservices/test_edit_service.py index 9fea6c4..90b2d83 100644 --- a/tests/test_odmservices/test_edit_service.py +++ b/tests/test_odmservices/test_edit_service.py @@ -72,7 +72,7 @@ def test_duplicate_values_filter(self): def test_save_series(self): stlen = len(self.series.data_values) assert self.edit_service.save() - val = self.series_service.get_series_by_id(self.series.id) + val = self.series_service.get_series(self.series.id) assert len(val.data_values)==stlen def test_save_as_series(self): @@ -96,7 +96,7 @@ def test_save_append_keep(self): svalue = self.series.data_values[0] self.edit_service.memDB.updateValue([svalue.local_date_time],'+', 5 ) - news= self.edit_service.memDB.series_service.get_series_by_id(self.series.id) + news= self.edit_service.memDB.series_service.get_series(self.series.id) result = self.edit_service.save_appending(overwrite = False) len2= len(self.series.data_values) assert len1 == len2 @@ -108,7 +108,7 @@ def test_save_append_overwrite(self): svalue = self.series.data_values[0] self.edit_service.memDB.updateValue([svalue.local_date_time],'+', 5) - news= self.edit_service.memDB.series_service.get_series_by_id(self.series.id) + news= self.edit_service.memDB.series_service.get_series(self.series.id) result = self.edit_service.save_appending(overwrite = True) len2= len(self.series.data_values) assert len1 == len2 diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index ec16828..e98b551 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -183,10 +183,10 @@ def test_get_all_series(self): assert series.id == all_series[0].id def test_get_series_by_id(self): - assert self.series_service.get_series_by_id(10) == None + assert self.series_service.get_series(10) == None series = test_util.add_series(self.session) - db_series = self.series_service.get_series_by_id(series.id) + db_series = self.series_service.get_series(series.id) assert series.id == db_series.id @@ -292,7 +292,7 @@ def test_delete_dvs(self): subset = dvs[:5] self.series_service.delete_dvs([x.local_date_time for x in subset]) assert self.series_service.get_data_value_by_id(subset[0].id) == None - series = self.series_service.get_series_by_id(series.id) # Reload + series = self.series_service.get_series(series.id) # Reload assert len(series.data_values) == 5 def test_update_dvs(self): @@ -304,7 +304,7 @@ def test_update_dvs(self): subset[i].data_value = 100 self.series_service.update_dvs(subset) - series = self.series_service.get_series_by_id(series.id) + series = self.series_service.get_series(series.id) assert series.data_values[0].data_value == 100 def test_create_new_series(self): @@ -338,7 +338,7 @@ def test_update_series(self): self.series_service.update_series(series) - series = self.series_service.get_series_by_id(series.id) + series = self.series_service.get_series(series.id) assert series.site_code == "NEW" assert series.variable_code == "NEW" @@ -367,16 +367,16 @@ def test_create_qcl(self): def test_delete_series(self): series = test_util.add_series(self.session) - assert self.series_service.get_series_by_id(series.id) != None + assert self.series_service.get_series(series.id) != None self.series_service.delete_series(series) - assert self.series_service.get_series_by_id(series.id) == None + assert self.series_service.get_series(series.id) == None def test_delete_values(self): series = test_util.add_series(self.session) - assert self.series_service.get_series_by_id(series.id) != None + assert self.series_service.get_series(series.id) != None self.series_service.delete_values_by_series(series) - val = self.series_service.get_series_by_id(series.id) + val = self.series_service.get_series(series.id) print val assert val != None From 0d32a791c9fb6b56276ecd5cd13b7099a36a3663 Mon Sep 17 00:00:00 2001 From: stephanie Date: Mon, 6 Feb 2017 16:29:02 -0700 Subject: [PATCH 154/158] fix save append --- odmtools/odmservices/edit_service.py | 7 +++++++ odmtools/odmservices/series_service.py | 28 ++++++-------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 8c27851..439a694 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -687,11 +687,18 @@ def overlapcalc(self, result, values, overwrite): # number of overlapping values overlapdf = values[(values["valuedatetime"] <= dfstart) & (values["valuedatetime"] >= dbend)] count = len(overlapdf) + # if not overwrite. remove any overlapping values from df if overlap: if not overwrite: + # delete overlapping from the data frame before saving to the database values = values[values["valuedatetime"] > dbend] + else: + # delete overlapping values from the series database + count = self.memDB.series_service.delete_values_by_series(str(values["resultid"]), dfstart) + + # return the number of overlapping values return count diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index c7c19d7..395b55d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -526,7 +526,8 @@ def insert_values(self, values): :param values: pandas dataframe :return: """ - values.to_sql(name="TimeSeriesResultValues", + setSchema(self._session_factory.engine) + values.to_sql(name=TimeSeriesResultValues.__tablename__, schema=TimeSeriesResultValues.__table_args__['schema'], if_exists='append', chunksize=1000, @@ -756,38 +757,21 @@ def get_all_affiliations(self): # raise e # # - def delete_values_by_series(self, series, startdate=None): + def delete_values_by_series(self, seriesid, startdate=None): """ :param series: :return: """ - # todo stephanie: add startdate stuff + try: - self.delete.deleteTSRValues(ids=[series.id]) + return self.delete.deleteTSRValues(ids=[seriesid], startdate=startdate) except Exception as ex: message = "Values were not successfully deleted: %s" % ex print message logger.error(message) raise ex - # try: - # q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - # variable_id = series.variable_id, - # method_id = series.method_id, - # source_id = series.source_id, - # quality_control_level_id = series.quality_control_level_id) - # if startdate is not None: - # #start date indicates what day you should start deleting values. the values will delete to the end of the series - # return q.filter(DataValue.local_date_time >= startdate).delete() - # else: - # return q.delete() - # - # except Exception as ex: - # message = "Values were not successfully deleted: %s" % ex - # print message - # logger.error(message) - # raise ex - # + def delete_dvs(self, id_list): """ From 577a6e60f555c85969e0ff7abd86df33bc9c1c9d Mon Sep 17 00:00:00 2001 From: stephanie Date: Tue, 14 Feb 2017 15:37:12 -0700 Subject: [PATCH 155/158] run macholib only if on a mac --- setup/setup.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/setup/setup.py b/setup/setup.py index d872c03..069cae0 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -11,23 +11,25 @@ python setup.py py2exe """ -import macholib -#print("~"*60 + "macholib verion: "+macholib.__version__) -if macholib.__version__ <= "1.7": - print("Applying macholib patch...") - import macholib.dyld - import macholib.MachOGraph - dyld_find_1_7 = macholib.dyld.dyld_find - def dyld_find(name, loader=None, **kwargs): - #print("~"*60 + "calling alternate dyld_find") - if loader is not None: - kwargs['loader_path'] = loader - return dyld_find_1_7(name, **kwargs) - macholib.MachOGraph.dyld_find = dyld_find - import sys import os +if sys.platform == 'darwin': + import macholib + #print("~"*60 + "macholib verion: "+macholib.__version__) + if macholib.__version__ <= "1.7": + print("Applying macholib patch...") + import macholib.dyld + import macholib.MachOGraph + dyld_find_1_7 = macholib.dyld.dyld_find + def dyld_find(name, loader=None, **kwargs): + #print("~"*60 + "calling alternate dyld_find") + if loader is not None: + kwargs['loader_path'] = loader + return dyld_find_1_7(name, **kwargs) + macholib.MachOGraph.dyld_find = dyld_find + + ''' from setuptools import setup From 0f0799d9df5c1eb0239c0217cdb059e298cfdab6 Mon Sep 17 00:00:00 2001 From: sreeder Date: Tue, 23 May 2017 17:06:48 -0600 Subject: [PATCH 156/158] cleanup unused code --- odmtools/odmservices/edit_service.py | 167 --------------------------- 1 file changed, 167 deletions(-) diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index 32125e1..ef3c3ae 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -730,173 +730,6 @@ def add_annotations(self, annolist): - - - # - # def updateSeries(self, result = None, is_new_series=False, overwrite = True, append = False): - # """ - # - # :param var: - # :param method: - # :param qcl: - # :param is_new_series: - # :return: - # """ - # - # result_id = result.ResultID if result is not None else None - # - # - # dvs = self.memDB.getDataValuesDF() - # if result_id is not None: - # dvs["ResultID"] = result_id - # - # - # #if is new series_service remove valueids - # #if is_new_series: - # dvs["ValueID"] = None - # ''' - # for dv in dvs: - # dv.id = None - # ''' - # - # series = self.memDB.series_service.get_series_by_id(self._series_id) - # logger.debug("original editing series_service id: %s" % str(series.id)) - # - # if (result): - # tseries = self.memDB.series_service.get_series(result_id) - # - # if tseries: - # logger.debug("Save existing series_service ID: %s" % str(tseries.id)) - # series = tseries - # else: - # print "Series doesn't exist (if you are not, you should be running SaveAs)" - # - # if is_new_series: - # series = series_module.copy_series(series) - # if var: - # series.variable_id = var_id - # series.variable_code = var.code - # series.variable_name = var.name - # series.speciation = var.speciation - # series.variable_units_id = var.variable_unit_id - # series.variable_units_name = var.variable_unit.name - # series.sample_medium = var.sample_medium - # series.value_type = var.value_type - # series.time_support = var.time_support - # series.time_units_id = var.time_unit_id - # series.time_units_name = var.time_unit.name - # series.data_type = var.data_type - # series.general_category = var.general_category - # - # if method: - # series.method_id = method_id - # series.method_description = method.description - # - # if qcl: - # series.quality_control_level_id = qcl_id - # series.quality_control_level_code = qcl.code - # ''' - # dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) - # dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) - # ''' - # - # form = "%Y-%m-%d %H:%M:%S" - # - # if not append: - # - # series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[c0].local_date_time - # series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time - # series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc - # series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc - # series.value_count = len(dvs) - # - # ## Override previous save - # if not is_new_series: - # # delete old dvs - # #pass - # self.memDB.series_service.delete_values_by_series(series) - # elif append: - # #if series_service end date is after dvs startdate - # dbend = series.end_date_time - # dfstart = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form) - # overlap = dbend>= dfstart - # #leave series_service start dates to those previously set - # series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form) - # series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) - # #TODO figure out how to calculate the new value count - # series.value_count = len(dvs) - # - # if overlap: - # if overwrite: - # #remove values from the database - # self.memDB.series_service.delete_values_by_series(series, startdate=dfstart) - # else: - # #remove values from df - # dvs = dvs[dvs["LocalDateTime"] > dbend] - # - # - # - # #logger.debug("series_service.data_values: %s" % ([x for x in series_service.data_values])) - # dvs.drop('ValueID', axis=1, inplace=True) - # return series, dvs - # - # def save(self): - # """ Save to an existing catalog - # :param var: - # :param method: - # :param qcl: - # :return: - # """ - # - # series, dvs = self.updateSeries(is_new_series=False) - # if self.memDB.series_service.save_series(series, dvs): - # logger.debug("series_service saved!") - # return True - # else: - # logger.debug("The Save was unsuccessful") - # return False - # - # def save_as(self, var=None, method=None, qcl=None): - # """ - # :param var: - # :param method: - # :param qcl: - # :return: - # """ - # series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) - # - # if self.memDB.series_service.save_new_series(series, dvs): - # logger.debug("series_service saved!") - # return True - # else: - # logger.debug("The Save As Function was Unsuccessful") - # return False - # - # def save_appending(self, var= None, method = None, qcl=None, overwrite=False): - # series, dvs = self.updateSeries(var, method, qcl, is_new_series=False, append= True, overwrite=overwrite) - # - # if self.memDB.series_service.save_series(series, dvs): - # logger.debug("series_service saved!") - # return True - # else: - # logger.debug("The Append Existing Function was Unsuccessful") - # return False - # - # def save_existing(self, var=None, method=None, qcl=None): - # """ - # :param var: - # :param method: - # :param qcl: - # :return: - # """ - # series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) - # if self.memDB.series_service.save_series(series, dvs): - # logger.debug("series_service saved!") - # return True - # else: - # logger.debug("The Save As Existing Function was Unsuccessful") - # return False - def create_qcl(self, code, definition, explanation): return self.memDB.series_service.create_processing_level(code, definition, explanation) From f93e21b473bb557456510cd1c2bf76addf2a7cb3 Mon Sep 17 00:00:00 2001 From: stephanie Date: Thu, 9 Nov 2017 09:27:10 -0700 Subject: [PATCH 157/158] update iceburg file to reflect 2.0 version --- setup/Mac/ODMTools.packproj | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/setup/Mac/ODMTools.packproj b/setup/Mac/ODMTools.packproj index 462f175..c3bf177 100644 --- a/setup/Mac/ODMTools.packproj +++ b/setup/Mac/ODMTools.packproj @@ -566,11 +566,9 @@ IFPkgDescriptionDescription IFPkgDescriptionTitle - - ODMTools_v1.2.8 + ODMTools_v2.0.1 IFPkgDescriptionVersion - 1.2.8 Beta - + 2.0.1 Beta Display Information @@ -586,7 +584,7 @@ CFBundleName ODMTools CFBundleShortVersionString - 1.2.8 + 2.0.1 Options @@ -612,16 +610,16 @@ Version IFMajorVersion - 1 - IFMinorVersion 2 + IFMinorVersion + 0 IFPkgFlagPackageSelection 0 Name - ODMTools_v1.2.8-beta_Mac_installer + ODMTools_v2.0.1-beta_Mac_installer Status 1 Type From ae9d692907046a5561bf431a1e9c018960b7f228 Mon Sep 17 00:00:00 2001 From: stephanie Date: Thu, 9 Nov 2017 09:51:36 -0700 Subject: [PATCH 158/158] update make file --- setup/make.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup/make.py b/setup/make.py index e5752c0..496952a 100644 --- a/setup/make.py +++ b/setup/make.py @@ -151,6 +151,7 @@ def run_pyinstaller(console=False): ## Console Version os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % WIN_DIR + '--workpath=%s ' % WORK_DIR + '--specpath=%s ' % WIN_DIR + @@ -167,6 +168,7 @@ def run_pyinstaller(console=False): val = os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % WIN_DIR + '--workpath=%s ' % WORK_DIR + '--specpath=%s ' % WIN_DIR + @@ -189,6 +191,7 @@ def mac_pyinstaller(): try: os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % MAC_DIST_DIR + '--workpath=%s ' % MAC_WORK_DIR + '--specpath=%s ' % MAC_DIR +