From 1604ea1f2c120700bce3bc8d862865b6568409ea Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 7 May 2017 02:02:54 -0400 Subject: [PATCH] Fix many bugs related to GUI not receiving a correct timestamp, as well as extending feature out to all other fit entities. --- eos/db/saveddata/cargo.py | 9 +++-- eos/db/saveddata/drone.py | 9 +++-- eos/db/saveddata/fit.py | 76 ++++++++++++++++++++++----------------- eos/events.py | 39 +++++++++++++++++--- eos/saveddata/fit.py | 11 ++++++ gui/mainFrame.py | 2 +- gui/shipBrowser.py | 2 +- service/fit.py | 9 +++-- 8 files changed, 112 insertions(+), 45 deletions(-) diff --git a/eos/db/saveddata/cargo.py b/eos/db/saveddata/cargo.py index 83ab0ca0b..ce24dd48c 100644 --- a/eos/db/saveddata/cargo.py +++ b/eos/db/saveddata/cargo.py @@ -18,11 +18,12 @@ # =============================================================================== from sqlalchemy import Table, Column, Integer, ForeignKey, DateTime -from sqlalchemy.orm import mapper +from sqlalchemy.orm import mapper, relation import sqlalchemy.sql.functions as func from eos.db import saveddata_meta from eos.saveddata.cargo import Cargo +from eos.saveddata.fit import Fit cargo_table = Table("cargo", saveddata_meta, Column("ID", Integer, primary_key=True), @@ -33,4 +34,8 @@ cargo_table = Table("cargo", saveddata_meta, Column("modified", DateTime, nullable=True, onupdate=func.now()), ) -mapper(Cargo, cargo_table) +mapper(Cargo, cargo_table, + properties={ + "owner": relation(Fit) + } +) diff --git a/eos/db/saveddata/drone.py b/eos/db/saveddata/drone.py index 93efd9956..d59c15fbe 100644 --- a/eos/db/saveddata/drone.py +++ b/eos/db/saveddata/drone.py @@ -18,11 +18,12 @@ # =============================================================================== from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime -from sqlalchemy.orm import mapper +from sqlalchemy.orm import mapper, relation import sqlalchemy.sql.functions as func from eos.db import saveddata_meta from eos.saveddata.drone import Drone +from eos.saveddata.fit import Fit drones_table = Table("drones", saveddata_meta, Column("groupID", Integer, primary_key=True), @@ -35,4 +36,8 @@ drones_table = Table("drones", saveddata_meta, Column("modified", DateTime, nullable=True, onupdate=func.now()) ) -mapper(Drone, drones_table) +mapper(Drone, drones_table, + properties={ + "owner": relation(Fit) + } +) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 9853f7247..372d230f4 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -58,8 +58,9 @@ fits_table = Table("fits", saveddata_meta, Column("modeID", Integer, nullable=True), Column("implantLocation", Integer, nullable=False, default=ImplantLocation.FIT), Column("notes", String, nullable=True), + Column("ignoreRestrictions", Boolean, default=0), Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("modified", DateTime, nullable=True, default=func.now(), onupdate=func.now()) ) projectedFits_table = Table("projectedFits", saveddata_meta, @@ -142,49 +143,70 @@ es_Fit._Fit__commandFits = association_proxy( "booster_fit", # .. and return the booster fit creator=lambda boosterID, booster_fit: CommandFit(boosterID, booster_fit) ) + + +# These relationships are broken out so that we can easily access it in the events stuff +# We sometimes don't want particular relationships to cause a fit modified update (eg: projecting +# a fit onto another would 'modify' both fits unless the following relationship is ignored) +projectedFitSourceRel = relationship( + ProjectedFit, + primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID, + backref='source_fit', + collection_class=attribute_mapped_collection('victimID'), + cascade='all, delete, delete-orphan') + + +boostedOntoRel = relationship( + CommandFit, + primaryjoin=commandFits_table.c.boosterID == fits_table.c.ID, + backref='booster_fit', + collection_class=attribute_mapped_collection('boostedID'), + cascade='all, delete, delete-orphan') + mapper(es_Fit, fits_table, properties={ - "_Fit__modules" : relation( + "_Fit__modules": relation( Module, collection_class=HandledModuleList, primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False), # noqa order_by=modules_table.c.position, cascade='all, delete, delete-orphan'), - "_Fit__projectedModules" : relation( + "_Fit__projectedModules": relation( Module, collection_class=HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)), # noqa - "owner" : relation( + "owner": relation( User, backref="fits"), - "itemID" : fits_table.c.shipID, - "shipID" : fits_table.c.shipID, - "_Fit__boosters" : relation( + "itemID": fits_table.c.shipID, + "shipID": fits_table.c.shipID, + "_Fit__boosters": relation( Booster, collection_class=HandledImplantBoosterList, cascade='all, delete, delete-orphan', + backref='owner', single_parent=True), - "_Fit__drones" : relation( + "_Fit__drones": relation( Drone, collection_class=HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)), # noqa - "_Fit__fighters" : relation( + "_Fit__fighters": relation( Fighter, collection_class=HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == False)), # noqa - "_Fit__cargo" : relation( + "_Fit__cargo": relation( Cargo, collection_class=HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)), - "_Fit__projectedDrones" : relation( + "_Fit__projectedDrones": relation( Drone, collection_class=HandledProjectedDroneList, cascade='all, delete, delete-orphan', @@ -196,46 +218,36 @@ mapper(es_Fit, fits_table, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == True)), # noqa - "_Fit__implants" : relation( + "_Fit__implants": relation( Implant, collection_class=HandledImplantBoosterList, cascade='all, delete, delete-orphan', - backref='fit', + backref='owner', single_parent=True, primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID, secondaryjoin=fitImplants_table.c.implantID == Implant.ID, secondary=fitImplants_table), - "_Fit__character" : relation( + "_Fit__character": relation( Character, backref="fits"), - "_Fit__damagePattern" : relation(DamagePattern), - "_Fit__targetResists" : relation(TargetResists), - "projectedOnto" : relationship( - ProjectedFit, - primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID, - backref='source_fit', - collection_class=attribute_mapped_collection('victimID'), - cascade='all, delete, delete-orphan'), - "victimOf" : relationship( + "_Fit__damagePattern": relation(DamagePattern), + "_Fit__targetResists": relation(TargetResists), + "projectedOnto": projectedFitSourceRel, + "victimOf": relationship( ProjectedFit, primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID, backref='victim_fit', collection_class=attribute_mapped_collection('sourceID'), cascade='all, delete, delete-orphan'), - "boostedOnto" : relationship( - CommandFit, - primaryjoin=commandFits_table.c.boosterID == fits_table.c.ID, - backref='booster_fit', - collection_class=attribute_mapped_collection('boostedID'), - cascade='all, delete, delete-orphan'), - "boostedOf" : relationship( + "boostedOnto": boostedOntoRel, + "boostedOf": relationship( CommandFit, primaryjoin=fits_table.c.ID == commandFits_table.c.boostedID, backref='boosted_fit', collection_class=attribute_mapped_collection('boosterID'), cascade='all, delete, delete-orphan'), } - ) +) mapper(ProjectedFit, projectedFits_table, properties={ @@ -244,5 +256,3 @@ mapper(ProjectedFit, projectedFits_table, ) mapper(CommandFit, commandFits_table) - - diff --git a/eos/events.py b/eos/events.py index 6284aee98..dd7a88a2b 100644 --- a/eos/events.py +++ b/eos/events.py @@ -2,9 +2,22 @@ import datetime from sqlalchemy.event import listen +from sqlalchemy.orm.collections import InstrumentedList + +from eos.db.saveddata.fit import projectedFitSourceRel, boostedOntoRel from eos.saveddata.fit import Fit from eos.saveddata.module import Module +from eos.saveddata.drone import Drone +from eos.saveddata.fighter import Fighter +from eos.saveddata.cargo import Cargo +from eos.saveddata.implant import Implant +from eos.saveddata.booster import Booster + +ignored_rels = [ + projectedFitSourceRel, + boostedOntoRel +] def update_fit_modified(target, value, oldvalue, initiator): @@ -12,18 +25,29 @@ def update_fit_modified(target, value, oldvalue, initiator): return if value != oldvalue: - print "{} had a change via {}".format(target.owner, target) - target.owner.modified = datetime.datetime.now() + # some things (like Implants) have a backref to the fit, which actually produces a list. + # In this situation, simply take the 0 index to get to the fit. + # There may be cases in the future in which there are multiple fits, so this should be + # looked at more indepth later + if isinstance(target.owner, InstrumentedList): + parent = target.owner[0] + else: + parent = target.owner + + # ensure this is a fit we're dealing with + if isinstance(parent, Fit): + parent.modified = datetime.datetime.now() def apply_col_listeners(target, context): - # We only want to se these events when the module is first loaded (otherwise events will fire during the initial + # We only want to set these events when the module is first loaded (otherwise events will fire during the initial # population of data). This runs through all columns and sets up "set" events on each column. We do it with each # column because the alternative would be to do a before/after_update for the Mapper itself, however we're only # allowed to change the local attributes during those events as that's inter-flush. # See http://docs.sqlalchemy.org/en/rel_1_0/orm/session_events.html#mapper-level-events # @todo replace with `inspect(Module).column_attrs` when mac binaries are updated + manager = getattr(target.__class__, "_sa_class_manager", None) if manager: for col in manager.mapper.column_attrs: @@ -48,8 +72,15 @@ def apply_rel_listeners(target, context): manager = getattr(target.__class__, "_sa_class_manager", None) if manager: for rel in manager.mapper.relationships: + if rel in ignored_rels: + continue listen(rel, 'append', rel_listener) listen(rel, 'remove', rel_listener) listen(Fit, 'load', apply_rel_listeners) -listen(Module, 'load', apply_col_listeners) \ No newline at end of file +listen(Module, 'load', apply_col_listeners) +listen(Drone, 'load', apply_col_listeners) +listen(Fighter, 'load', apply_col_listeners) +listen(Cargo, 'load', apply_col_listeners) +listen(Implant, 'load', apply_col_listeners) +listen(Booster, 'load', apply_col_listeners) \ No newline at end of file diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 014c69fa6..cdeab2f58 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -21,6 +21,7 @@ import time from copy import deepcopy from itertools import chain from math import sqrt, log, asinh +import datetime from sqlalchemy.orm import validates, reconstructor @@ -76,6 +77,8 @@ class Fit(object): self.projected = False self.name = name self.timestamp = time.time() + self.created = None + self.modified = None self.modeID = None self.build() @@ -179,6 +182,14 @@ class Fit(object): self.__mode = mode self.modeID = mode.item.ID if mode is not None else None + @property + def modifiedCoalesce(self): + """ + This is a property that should get whichever date is available for the fit. @todo: migrate old timestamp data + and ensure created / modified are set in database to get rid of this + """ + return self.modified or self.created or datetime.datetime.fromtimestamp(self.timestamp) + @property def character(self): return self.__character if self.__character is not None else Character.getAll0() diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 8ef05641c..4c2074641 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -926,7 +926,7 @@ class MainFrame(wx.Frame): results.append(( fit.ID, fit.name, - fit.modified or fit.created or datetime.datetime.fromtimestamp(fit.timestamp), + fit.modifiedCoalesce, fit.ship.item )) wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True)) diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 958c5e07f..54fd39f9c 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -1994,7 +1994,7 @@ class FitItem(SFItem.SFBrowserItem): if activeFit == self.fitID: sFit = Fit.getInstance() fit = sFit.getFit(activeFit) - self.timestamp = fit.modified + self.timestamp = fit.modifiedCoalesce SFItem.SFBrowserItem.Refresh(self) diff --git a/service/fit.py b/service/fit.py index 4d2ac94ca..6bf35951f 100644 --- a/service/fit.py +++ b/service/fit.py @@ -267,10 +267,15 @@ class Fit(object): pyfalog.debug("Searching for fit: {0}", name) results = eos.db.searchFits(name) fits = [] + for fit in results: fits.append(( - fit.ID, fit.name, fit.ship.item.ID, fit.ship.item.name, fit.booster, - fit.timestamp)) + fit.ID, + fit.name, + fit.ship.item.ID, + fit.ship.item.name, + fit.booster, + fit.modifiedCoalesce)) return fits def addImplant(self, fitID, itemID, recalc=True):