From e94b4a1c1844d8f12e13a4053c92c810db4bdf9d Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 21 Apr 2017 23:43:40 -0400 Subject: [PATCH 1/8] Fix old issue with trying to open previous fits that don't exist. --- gui/mainFrame.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 8a66e78d2e..5e1ad08c6a 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -255,7 +255,9 @@ def LoadPreviousOpenFits(self): # Remove any fits that cause exception when fetching (non-existent fits) for id in fits[:]: try: - sFit.getFit(id, basic=True) + fit = sFit.getFit(id, basic=True) + if fit is None: + fits.remove(id) except: fits.remove(id) From b4930d15b78a2c62f464460cc07ceedba8c9280e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 22 Apr 2017 18:58:30 -0400 Subject: [PATCH 2/8] Update Fit.modified whenever modules change, allowing proper "recent" timestamps --- eos/db/saveddata/fit.py | 24 ++++++++++++++++++++++++ eos/db/saveddata/module.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 959c992872..9d8de9b7fe 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -17,9 +17,12 @@ # along with eos. If not, see . # =============================================================================== +import datetime +from sqlalchemy import inspect from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql import and_ +from sqlalchemy.event import listen from sqlalchemy.orm import relation, reconstructor, mapper, relationship from sqlalchemy import ForeignKey, Column, Integer, String, Table, Boolean, DateTime import sqlalchemy.sql.functions as func @@ -244,3 +247,24 @@ def __repr__(self): ) mapper(CommandFit, commandFits_table) + + +def rel_listener(target, value, initiator): + if not target or (isinstance(value, Module) and value.isEmpty): + return + + print "{} has has has a relationship changes :(".format(target) + target.modified = datetime.datetime.now() + + +def load_listener(target, context): + # We only want to se these events when the fit is first loaded (otherwise events will fire during the initial + # population of data). This sets listeners for all the relationships on fits. This allows us to update the fit's + # modified date whenever something is added/removed from fit + # See http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load + for rel in inspect(es_Fit).relationships: + listen(rel, 'append', rel_listener) + listen(rel, 'remove', rel_listener) + +listen(Module, 'load', load_listener) + diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py index 7f869c0a6e..6e4da73b46 100644 --- a/eos/db/saveddata/module.py +++ b/eos/db/saveddata/module.py @@ -17,10 +17,14 @@ # along with eos. If not, see . # =============================================================================== -from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime +from sqlalchemy import inspect +from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime, select from sqlalchemy.orm import relation, mapper +from sqlalchemy.event import listen import sqlalchemy.sql.functions as func +import datetime + from eos.db import saveddata_meta from eos.saveddata.module import Module from eos.saveddata.fit import Fit @@ -40,3 +44,27 @@ mapper(Module, modules_table, properties={"owner": relation(Fit)}) + + +def update_fit_modified(target, value, oldvalue, initiator): + if not target.owner: + return + + if value != oldvalue: + print "{} had a module change".format(target.owner) + target.owner.modified = datetime.datetime.now() + + +def my_load_listener(target, context): + # We only want to se 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 + for col in inspect(Module).column_attrs: + listen(col, 'set', update_fit_modified) + + +listen(Module, 'load', my_load_listener) + + From ae99a179d94ff9f508814b130b463ab00dc7433c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 22 Apr 2017 19:30:50 -0400 Subject: [PATCH 3/8] Update FitItem to use modified datetime of fit (fallback to created and old timestamp). Remove activation of FitItem timer (no idea what this is trying to do???) --- gui/shipBrowser.py | 12 +++++++++--- service/fit.py | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 4fe421082e..594fefe97e 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -7,6 +7,7 @@ import wx # noinspection PyPackageRequirements from wx.lib.buttons import GenBitmapButton +import datetime from service.fit import Fit from service.market import Market @@ -953,7 +954,7 @@ def importStage(self, event): shipTrait, fit.name, fit.booster, - fit.timestamp, + fit.modified, ), fit.ship.item.ID, )) @@ -1473,6 +1474,7 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn self.shipFittingInfo = shipFittingInfo self.shipName, self.shipTrait, self.fitName, self.fitBooster, self.timestamp = shipFittingInfo + self.shipTrait = re.sub("<.*?>", " ", self.shipTrait) # see GH issue #62 @@ -1553,10 +1555,13 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn # self.animCount = 0 # ===================================================================== + """ + # Remove this bit as the time stuff is non-functional (works... but not exactly sure what it's meant to do) self.selTimerID = wx.NewId() self.selTimer = wx.Timer(self, self.selTimerID) self.selTimer.Start(100) + """ self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab) @@ -1645,6 +1650,7 @@ def GetType(self): def OnTimer(self, event): + # @todo: figure out what exactly this is supposed to accomplish if self.selTimerID == event.GetId(): ctimestamp = time.time() interval = 5 @@ -1893,8 +1899,8 @@ def DrawItem(self, mdc): mdc.SetFont(self.fontNormal) - fitDate = time.localtime(self.timestamp) - fitLocalDate = "%d/%02d/%02d %02d:%02d" % (fitDate[0], fitDate[1], fitDate[2], fitDate[3], fitDate[4]) + fitDate = self.timestamp.strftime("%m/%d/%Y %H:%M") + fitLocalDate = fitDate #"%d/%02d/%02d %02d:%02d" % (fitDate[0], fitDate[1], fitDate[2], fitDate[3], fitDate[4]) pfdate = drawUtils.GetPartialText(mdc, fitLocalDate, self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw) diff --git a/service/fit.py b/service/fit.py index b14250f179..112a790c9d 100644 --- a/service/fit.py +++ b/service/fit.py @@ -20,6 +20,7 @@ import copy from logbook import Logger from time import time +import datetime import eos.db from eos.saveddata.booster import Booster as es_Booster @@ -93,7 +94,7 @@ def getFitsWithShip(shipID): fits = eos.db.getFitsWithShip(shipID) names = [] for fit in fits: - names.append((fit.ID, fit.name, fit.booster, fit.timestamp)) + names.append((fit.ID, fit.name, fit.booster, fit.modified or fit.created or datetime.datetime.fromtimestamp(fit.timestamp))) return names From bb73065b4377ccb2717778bed610e24c68cef61b Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 22 Apr 2017 22:46:23 -0400 Subject: [PATCH 4/8] Update FitItem datetime when fit changes --- gui/shipBrowser.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 594fefe97e..59cbd96b91 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -1534,6 +1534,7 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn self.tcFitName.Bind(wx.EVT_TEXT_ENTER, self.renameFit) self.tcFitName.Bind(wx.EVT_KILL_FOCUS, self.editLostFocus) self.tcFitName.Bind(wx.EVT_KEY_DOWN, self.editCheckEsc) + self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged) self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) self.animTimerId = wx.NewId() @@ -1566,6 +1567,13 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab) + def OnFitChanged(self, evt): + if evt.fitID == self.fitID: + sFit = Fit.getInstance() + fit = sFit.getFit(evt.fitID) + if fit.modified: + self.timestamp = fit.modified + def OpenNewTab(self, evt): self.selectFit(newTab=True) @@ -1732,7 +1740,6 @@ def renameFit(self, event=None): self.fitName = fitName sFit.renameFit(self.fitID, self.fitName) wx.PostEvent(self.mainFrame, FitRenamed(fitID=self.fitID)) - self.Refresh() else: self.tcFitName.SetValue(self.fitName) From 32f417ce5a9eec8f2423789718e722dcb228612c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 23 Apr 2017 20:20:04 -0400 Subject: [PATCH 5/8] Created first iteration of "Recent Fits" view, in which a nav button is interacted with to show a list of the 100 most recently modified fits. Ship Broswer is littered with ToggleRecentShips() to reset the icon when not in "recent" mode. This should probably be fixed at some point. Removed the FIT_CHANGED binding from FitItem - this was causing very odd issues when the object was destroyed (navigating to another "stage") such as the Fit Changed event for that fit no longer firing (or at least seemingly so) To fix this, simply look at the active fit during FitItem.Refresh() Also creates a new query to get a list of items from the DB, although it's not used (was gonna use it, decided against it, but didn't want to delete the code - could prove handy later) --- eos/db/gamedata/queries.py | 31 +++++++++++++- eos/db/saveddata/fit.py | 17 ++++---- eos/db/saveddata/module.py | 13 +++--- eos/db/saveddata/queries.py | 17 ++++++++ gui/mainFrame.py | 12 +++++- gui/shipBrowser.py | 81 ++++++++++++++++++++++++++---------- imgs/gui/frecent_small.png | Bin 0 -> 793 bytes service/fit.py | 14 +++++++ 8 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 imgs/gui/frecent_small.png diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py index 30f51fe6b3..7c5ca3397d 100644 --- a/eos/db/gamedata/queries.py +++ b/eos/db/gamedata/queries.py @@ -27,12 +27,11 @@ from eos.db.util import processEager, processWhere from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData +cache = {} configVal = getattr(eos.config, "gamedataCache", None) if configVal is True: def cachedQuery(amount, *keywords): def deco(function): - cache = {} - def checkAndReturn(*args, **kwargs): useCache = kwargs.pop("useCache", True) cacheKey = [] @@ -98,6 +97,34 @@ def getItem(lookfor, eager=None): return item +@cachedQuery(1, "lookfor") +def getItems(lookfor, eager=None): + """ + Gets a list of items. Does a bit of cache hackery to get working properly -- cache + is usually based on function calls with the parameters, needed to extract data directly. + Works well enough. Not currently used, but it's here for possible future inclusion + """ + + toGet = [] + results = [] + + for id in lookfor: + if (id, None) in cache: + results.append(cache.get((id, None))) + else: + toGet.append(id) + + if len(toGet) > 0: + # Get items that aren't currently cached, and store them in the cache + items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all() + for item in items: + cache[(item.ID, None)] = item + results += items + + # sort the results based on the original indexing + results.sort(key=lambda x: lookfor.index(x.ID)) + return results + @cachedQuery(1, "lookfor") def getAlphaClone(lookfor, eager=None): if isinstance(lookfor, int): diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 9d8de9b7fe..5f3c12879a 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -18,7 +18,6 @@ # =============================================================================== import datetime -from sqlalchemy import inspect from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql import and_ @@ -244,7 +243,7 @@ def __repr__(self): properties={ "_ProjectedFit__amount": projectedFits_table.c.amount, } - ) +) mapper(CommandFit, commandFits_table) @@ -253,18 +252,22 @@ def rel_listener(target, value, initiator): if not target or (isinstance(value, Module) and value.isEmpty): return - print "{} has has has a relationship changes :(".format(target) + print "{} has had a relationship change :D".format(target) target.modified = datetime.datetime.now() def load_listener(target, context): - # We only want to se these events when the fit is first loaded (otherwise events will fire during the initial + # We only want to see these events when the fit is first loaded (otherwise events will fire during the initial # population of data). This sets listeners for all the relationships on fits. This allows us to update the fit's # modified date whenever something is added/removed from fit # See http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load - for rel in inspect(es_Fit).relationships: - listen(rel, 'append', rel_listener) - listen(rel, 'remove', rel_listener) + + # todo: when we can, move over to `inspect(es_Fit).relationships` (when mac binaries are updated) + manager = getattr(es_Fit, "_sa_class_manager", None) + if manager: + for rel in manager.mapper.relationships: + listen(rel, 'append', rel_listener) + listen(rel, 'remove', rel_listener) listen(Module, 'load', load_listener) diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py index 6e4da73b46..6993e53fa2 100644 --- a/eos/db/saveddata/module.py +++ b/eos/db/saveddata/module.py @@ -17,7 +17,6 @@ # along with eos. If not, see . # =============================================================================== -from sqlalchemy import inspect from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime, select from sqlalchemy.orm import relation, mapper from sqlalchemy.event import listen @@ -55,16 +54,20 @@ def update_fit_modified(target, value, oldvalue, initiator): target.owner.modified = datetime.datetime.now() -def my_load_listener(target, context): +def load_listener(target, context): # We only want to se 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 - for col in inspect(Module).column_attrs: - listen(col, 'set', update_fit_modified) + # @todo replace with `inspect(Module).column_attrs` when mac binaries are updated + manager = getattr(Module, "_sa_class_manager", None) + if manager: + for col in manager.mapper.column_attrs: + listen(col, 'set', update_fit_modified) -listen(Module, 'load', my_load_listener) + +listen(Module, 'load', load_listener) diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index 1088c1b0f4..e82d2f744a 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -18,6 +18,7 @@ # =============================================================================== from sqlalchemy.sql import and_ +from sqlalchemy import desc, select from eos.db import saveddata_session, sd_lock from eos.db.saveddata.fit import projectedFits_table @@ -242,6 +243,22 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None): return fits +def getRecentFits(ownerID=None, where=None, eager=None): + eager = processEager(eager) + with sd_lock: + q = select(( + Fit.ID, + Fit.shipID, + Fit.name, + Fit.modified, + Fit.created, + Fit.timestamp + )).order_by(desc(Fit.modified), desc(Fit.timestamp)).limit(50) + fits = eos.db.saveddata_session.execute(q).fetchall() + + return fits + + def getFitsWithModules(typeIDs, eager=None): """ Get all the fits that have typeIDs fitted to them diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 5e1ad08c6a..8ef05641c0 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -20,6 +20,7 @@ import sys import os.path from logbook import Logger +import datetime import sqlalchemy # noinspection PyPackageRequirements @@ -919,7 +920,16 @@ def _openAfterImport(self, fits): wx.PostEvent(self, FitSelected(fitID=fit.ID)) wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True)) else: - wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True)) + fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name)) + results = [] + for fit in fits: + results.append(( + fit.ID, + fit.name, + fit.modified or fit.created or datetime.datetime.fromtimestamp(fit.timestamp), + fit.ship.item + )) + wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True)) def closeProgressDialog(self): # Windows apparently handles ProgressDialogs differently. We can diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 59cbd96b91..78f12b26b3 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -338,15 +338,21 @@ def __init__(self, parent, size=(-1, 24)): self.newBmpH = BitmapLoader.getBitmap("fit_add_small", "gui") self.resetBmpH = BitmapLoader.getBitmap("freset_small", "gui") self.switchBmpH = BitmapLoader.getBitmap("fit_switch_view_mode_small", "gui") + self.recentBmpH = BitmapLoader.getBitmap("frecent_small", "gui") switchImg = BitmapLoader.getImage("fit_switch_view_mode_small", "gui") switchImg = switchImg.AdjustChannels(1, 1, 1, 0.4) self.switchBmpD = wx.BitmapFromImage(switchImg) + recentImg = BitmapLoader.getImage("frecent_small", "gui") + recentImg = recentImg.AdjustChannels(1, 1, 1, 0.4) + self.recentBmpD = wx.BitmapFromImage(recentImg) + self.resetBmp = self.AdjustChannels(self.resetBmpH) self.rewBmp = self.AdjustChannels(self.rewBmpH) self.searchBmp = self.AdjustChannels(self.searchBmpH) self.switchBmp = self.AdjustChannels(self.switchBmpH) + self.recentBmp = self.AdjustChannels(self.recentBmpH) self.newBmp = self.AdjustChannels(self.newBmpH) self.toolbar.AddButton(self.resetBmp, "Ship groups", clickCallback=self.OnHistoryReset, @@ -357,6 +363,9 @@ def __init__(self, parent, size=(-1, 24)): self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups", clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH, show=False) + self.btnRecent = self.toolbar.AddButton(self.recentBmpD, "Recent Fits", + clickCallback=self.ToggleRecentShips, hoverBitmap=self.recentBmpH, + show=True) modifier = "CTRL" if 'wxMac' not in wx.PlatformInfo else "CMD" self.toolbar.AddButton(self.searchBmp, "Search fittings ({}+F)".format(modifier), clickCallback=self.ToggleSearchBox, @@ -416,6 +425,27 @@ def OnBrowserSearchBoxEsc(self, event): def OnResize(self, event): self.Refresh() + def ToggleRecentShips(self, bool = None, emitEvent = True): + # this is so janky. Need to revaluate pretty much entire ship browser. >.< + toggle = bool if bool is not None else not self.shipBrowser.recentFits + + if not toggle: + self.shipBrowser.recentFits = False + self.btnRecent.label = "Recent Fits" + self.btnRecent.normalBmp = self.recentBmpD + + if emitEvent: + wx.PostEvent(self.shipBrowser, Stage1Selected()) + else: + self.shipBrowser.recentFits = True + self.btnRecent.label = "Hide Recent Fits" + self.btnRecent.normalBmp = self.recentBmp + + if emitEvent: + sFit = Fit.getInstance() + fits = sFit.getRecentFits() + wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True, recent=True)) + def ToggleEmptyGroupsView(self): if self.shipBrowser.filterShipsWithNoFits: self.shipBrowser.filterShipsWithNoFits = False @@ -454,11 +484,13 @@ def OnNewFitting(self): wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def OnHistoryReset(self): + self.ToggleRecentShips(False, False) if self.shipBrowser.browseHist: self.shipBrowser.browseHist = [] self.gotoStage(1, 0) def OnHistoryBack(self): + self.ToggleRecentShips(False, False) if len(self.shipBrowser.browseHist) > 0: stage, data = self.shipBrowser.browseHist.pop() self.gotoStage(stage, data) @@ -538,6 +570,7 @@ def RenderBackground(self): self.bkBitmap.mFactor = mFactor def gotoStage(self, stage, data=None): + self.shipBrowser.recentFits = False if stage == 1: wx.PostEvent(self.Parent, Stage1Selected()) elif stage == 2: @@ -573,6 +606,7 @@ def __init__(self, parent): self._stage3ShipName = "" self.fitIDMustEditName = -1 self.filterShipsWithNoFits = False + self.recentFits = False self.racesFilter = {} @@ -629,7 +663,8 @@ def RefreshContent(self): def RefreshList(self, event): stage = self.GetActiveStage() - if stage == 3 or stage == 4: + + if stage in (3, 4, 5): self.lpane.RefreshList(True) event.Skip() @@ -672,6 +707,7 @@ def GetRaceFilterState(self, race): return self.racesFilter[race] def stage1(self, event): + self.navpanel.ToggleRecentShips(False, False) self._lastStage = self._activeStage self._activeStage = 1 self.lastdata = 0 @@ -727,6 +763,7 @@ def raceNameKey(self, ship): def stage2Callback(self, data): if self.GetActiveStage() != 2: return + self.navpanel.ToggleRecentShips(False, False) categoryID = self._stage2Data ships = list(data[1]) @@ -812,7 +849,7 @@ def nameKey(info): return info[1] def stage3(self, event): - + self.navpanel.ToggleRecentShips(False, False) self.lpane.ShowLoading(False) # If back is False, do not append to history. This could be us calling @@ -921,6 +958,10 @@ def searchStage(self, event): self.Layout() def importStage(self, event): + """ + The import stage handles both displaying fits after importing as well as displaying recent fits. todo: need to + reconcile these two better into a more uniform function, right now hacked together to get working + """ self.lpane.ShowLoading(False) self.navpanel.ShowNewFitButton(False) @@ -934,29 +975,26 @@ def importStage(self, event): fits = event.fits - # sort by ship name, then fit name - fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name)) - self.lastdata = fits self.lpane.Freeze() self.lpane.RemoveAllChildren() if fits: for fit in fits: - shipTrait = fit.ship.item.traits.traitText if (fit.ship.item.traits is not None) else "" - # empty string if no traits + shipItem = fit[3] + shipTrait = shipItem.traits.traitText if (shipItem.traits is not None) else "" self.lpane.AddWidget(FitItem( self.lpane, - fit.ID, + fit[0], ( - fit.ship.item.name, + shipItem.name, shipTrait, - fit.name, - fit.booster, - fit.modified, + fit[1], + False, + fit[2] ), - fit.ship.item.ID, + shipItem.ID, )) self.lpane.RefreshList(doFocus=False) self.lpane.Thaw() @@ -1534,7 +1572,6 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn self.tcFitName.Bind(wx.EVT_TEXT_ENTER, self.renameFit) self.tcFitName.Bind(wx.EVT_KILL_FOCUS, self.editLostFocus) self.tcFitName.Bind(wx.EVT_KEY_DOWN, self.editCheckEsc) - self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged) self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) self.animTimerId = wx.NewId() @@ -1567,13 +1604,6 @@ def __init__(self, parent, fitID=None, shipFittingInfo=("Test", "TestTrait", "cn self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab) - def OnFitChanged(self, evt): - if evt.fitID == self.fitID: - sFit = Fit.getInstance() - fit = sFit.getFit(evt.fitID) - if fit.modified: - self.timestamp = fit.modified - def OpenNewTab(self, evt): self.selectFit(newTab=True) @@ -1960,6 +1990,15 @@ def GetState(self): state = SFItem.SB_ITEM_NORMAL return state + def Refresh(self): + activeFit = self.mainFrame.getActiveFit() + if activeFit == self.fitID: + sFit = Fit.getInstance() + fit = sFit.getFit(activeFit) + self.timestamp = fit.modified + + SFItem.SFBrowserItem.Refresh(self) + def RenderBackground(self): rect = self.GetRect() diff --git a/imgs/gui/frecent_small.png b/imgs/gui/frecent_small.png new file mode 100644 index 0000000000000000000000000000000000000000..911da3f1d31fca4494a4beb22014e5c5c724c236 GIT binary patch literal 793 zcmV+!1LpjRP)`EB*FHYdKr%;k=xO&(k^EfNlSiKZ>5l+xr|%SFOV@6-ysFmD2F5 ze93OiS+LaQym;|2f6tbH%~V`D+ND?vc>4J^KSLxEMifJQ`8>*~y^+pGr&o-n=LJ zGWB(yB#;DR8&Lhqi{0(#wc#SwSB~jZKzIFx`8od>2Fo-Pfe7*M8^q#qw2yTxiXzd~ zRaz|F*rr78G`JZXG*YX~5K@5k>G@0HdlBo-6v1jHye?%qRwO@+-hO7J^4LlPG>@A1#{ zQFl4x7tnG)+cz_2Mq_f*H!U)kgg{iHqxT)Yr3ec@K!`)z_%h1c0Y2Eu(dMPkrhq5v zY+bKWfx|sTiOEB71HuwS*CDzA%ReBv4*7Zy7RM0n6`5#chfOJK`ze)Y6>d6Z?UmyNHH!3DdsP-ARyDo}1HO+>7_um7 zx_gj{+_aU_OUH_~Jd?KI#ICZujD`of2mDpCv_zFGE%6}tfL|j!WGu_i-u>4%{%d{$ X7`zMSfT21V00000NkvXXu0mjfkBx0` literal 0 HcmV?d00001 diff --git a/service/fit.py b/service/fit.py index 112a790c9d..4d2ac94ca4 100644 --- a/service/fit.py +++ b/service/fit.py @@ -98,6 +98,20 @@ def getFitsWithShip(shipID): return names + @staticmethod + def getRecentFits(): + """ Fetches recently modified fits, used with shipBrowser """ + pyfalog.debug("Fetching recent fits") + fits = eos.db.getRecentFits() + returnInfo = [] + + for fit in fits: + item = eos.db.getItem(fit[1]) + returnInfo.append((fit[0], fit[2], fit[3] or fit[4] or datetime.datetime.fromtimestamp(fit[5]), item)) + # ID name timestamps + + return returnInfo + @staticmethod def getFitsWithModules(typeIDs): """ Lists fits flagged as booster """ From f9b7376cc78fce401ad885026a753f68c6e0ddd3 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 26 Apr 2017 19:34:47 -0400 Subject: [PATCH 6/8] Move sql alchemy events into their own file for now so that mac-deprecated doesn't get pissy. --- eos/db/saveddata/fit.py | 25 ----------------- eos/db/saveddata/module.py | 28 ------------------- eos/events.py | 55 ++++++++++++++++++++++++++++++++++++++ gui/shipBrowser.py | 1 - pyfa.py | 7 +++++ 5 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 eos/events.py diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 5f3c12879a..9853f72477 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -17,11 +17,9 @@ # along with eos. If not, see . # =============================================================================== -import datetime from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql import and_ -from sqlalchemy.event import listen from sqlalchemy.orm import relation, reconstructor, mapper, relationship from sqlalchemy import ForeignKey, Column, Integer, String, Table, Boolean, DateTime import sqlalchemy.sql.functions as func @@ -248,26 +246,3 @@ def __repr__(self): mapper(CommandFit, commandFits_table) -def rel_listener(target, value, initiator): - if not target or (isinstance(value, Module) and value.isEmpty): - return - - print "{} has had a relationship change :D".format(target) - target.modified = datetime.datetime.now() - - -def load_listener(target, context): - # We only want to see these events when the fit is first loaded (otherwise events will fire during the initial - # population of data). This sets listeners for all the relationships on fits. This allows us to update the fit's - # modified date whenever something is added/removed from fit - # See http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load - - # todo: when we can, move over to `inspect(es_Fit).relationships` (when mac binaries are updated) - manager = getattr(es_Fit, "_sa_class_manager", None) - if manager: - for rel in manager.mapper.relationships: - listen(rel, 'append', rel_listener) - listen(rel, 'remove', rel_listener) - -listen(Module, 'load', load_listener) - diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py index 6993e53fa2..fbbf9332a2 100644 --- a/eos/db/saveddata/module.py +++ b/eos/db/saveddata/module.py @@ -19,11 +19,8 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime, select from sqlalchemy.orm import relation, mapper -from sqlalchemy.event import listen import sqlalchemy.sql.functions as func -import datetime - from eos.db import saveddata_meta from eos.saveddata.module import Module from eos.saveddata.fit import Fit @@ -45,29 +42,4 @@ properties={"owner": relation(Fit)}) -def update_fit_modified(target, value, oldvalue, initiator): - if not target.owner: - return - - if value != oldvalue: - print "{} had a module change".format(target.owner) - target.owner.modified = datetime.datetime.now() - - -def load_listener(target, context): - # We only want to se 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(Module, "_sa_class_manager", None) - if manager: - for col in manager.mapper.column_attrs: - listen(col, 'set', update_fit_modified) - - -listen(Module, 'load', load_listener) - diff --git a/eos/events.py b/eos/events.py new file mode 100644 index 0000000000..6284aee98b --- /dev/null +++ b/eos/events.py @@ -0,0 +1,55 @@ +# Decided to put this in it's own file so that we can easily choose not to import it (thanks to mac-deprecated builds =/) + +import datetime +from sqlalchemy.event import listen + +from eos.saveddata.fit import Fit +from eos.saveddata.module import Module + + +def update_fit_modified(target, value, oldvalue, initiator): + if not target.owner: + return + + if value != oldvalue: + print "{} had a change via {}".format(target.owner, target) + target.owner.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 + # 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: + listen(col, 'set', update_fit_modified) + + +def rel_listener(target, value, initiator): + if not target or (isinstance(value, Module) and value.isEmpty): + return + + print "{} has had a relationship change :D".format(target) + target.modified = datetime.datetime.now() + + +def apply_rel_listeners(target, context): + # We only want to see these events when the fit is first loaded (otherwise events will fire during the initial + # population of data). This sets listeners for all the relationships on fits. This allows us to update the fit's + # modified date whenever something is added/removed from fit + # See http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load + + # todo: when we can, move over to `inspect(es_Fit).relationships` (when mac binaries are updated) + manager = getattr(target.__class__, "_sa_class_manager", None) + if manager: + for rel in manager.mapper.relationships: + 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 diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 78f12b26b3..958c5e07f5 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -7,7 +7,6 @@ import wx # noinspection PyPackageRequirements from wx.lib.buttons import GenBitmapButton -import datetime from service.fit import Fit from service.market import Market diff --git a/pyfa.py b/pyfa.py index 9367f67ab6..2fec5fb2ac 100755 --- a/pyfa.py +++ b/pyfa.py @@ -341,6 +341,7 @@ def handleGUIException(exc_type, exc_value, exc_traceback): else: saVersion = sqlalchemy.__version__ saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) + config.saVersion = (int(saMatch.group(1)), int(saMatch.group(2)), int(saMatch.group(4))) if saMatch: saMajor = int(saMatch.group(1)) saMinor = int(saMatch.group(2)) @@ -370,6 +371,12 @@ def handleGUIException(exc_type, exc_value, exc_traceback): pyfalog.warning("Recommended version {0} {1}", requirement_parsed[1], requirement_parsed[2]) import eos.db + + if config.saVersion[0] > 0 or config.saVersion[1] >= 7: + # <0.7 doesn't have support for events ;_; (mac-deprecated) + config.sa_events = True + import eos.events + # noinspection PyUnresolvedReferences import service.prefetch # noqa: F401 From 1604ea1f2c120700bce3bc8d862865b6568409ea Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 7 May 2017 02:02:54 -0400 Subject: [PATCH 7/8] 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 83ab0ca0b8..ce24dd48c7 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 @@ 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 93efd99562..d59c15fbe2 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 @@ 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 9853f72477..372d230f44 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -58,8 +58,9 @@ 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 @@ def __repr__(self): "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 @@ def __repr__(self): 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 @@ def __repr__(self): ) mapper(CommandFit, commandFits_table) - - diff --git a/eos/events.py b/eos/events.py index 6284aee98b..dd7a88a2b4 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 014c69fa60..cdeab2f58d 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -21,6 +21,7 @@ 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 @@ def __init__(self, ship=None, name=""): 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 @@ def mode(self, mode): 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 8ef05641c0..4c2074641d 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -926,7 +926,7 @@ def _openAfterImport(self, fits): 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 958c5e07f5..54fd39f9cc 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -1994,7 +1994,7 @@ def Refresh(self): 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 4d2ac94ca4..6bf35951f1 100644 --- a/service/fit.py +++ b/service/fit.py @@ -267,10 +267,15 @@ def searchFits(name): 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): From f1eb3f68d186c39e5a5920257b13157f04118fd1 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 7 May 2017 11:35:26 -0400 Subject: [PATCH 8/8] Change func.now() to datetime.datetime.now(). Apparently these are calculated differently (sqlite function is in utc, datetime is in local), and I couldn't figure out how to retrieve the tiome back into local. Since this is a single user application without a concern for time drift on the server, this should work just as well. --- eos/db/saveddata/booster.py | 6 +++--- eos/db/saveddata/cargo.py | 6 +++--- eos/db/saveddata/character.py | 6 +++--- eos/db/saveddata/crest.py | 4 ++-- eos/db/saveddata/damagePattern.py | 6 +++--- eos/db/saveddata/drone.py | 6 +++--- eos/db/saveddata/fighter.py | 6 +++--- eos/db/saveddata/fit.py | 14 +++++++------- eos/db/saveddata/implant.py | 6 +++--- eos/db/saveddata/implantSet.py | 6 +++--- eos/db/saveddata/module.py | 6 +++--- eos/db/saveddata/override.py | 6 +++--- eos/db/saveddata/skill.py | 6 +++--- eos/db/saveddata/targetResists.py | 6 +++--- 14 files changed, 45 insertions(+), 45 deletions(-) diff --git a/eos/db/saveddata/booster.py b/eos/db/saveddata/booster.py index 518bca0030..e40762cd91 100644 --- a/eos/db/saveddata/booster.py +++ b/eos/db/saveddata/booster.py @@ -20,7 +20,7 @@ from sqlalchemy import Table, Column, ForeignKey, Integer, Boolean, DateTime from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import mapper, relation -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.booster import Booster @@ -30,8 +30,8 @@ Column("itemID", Integer), Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False), Column("active", Boolean), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()), + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), ) # Legacy booster side effect code, should disable but a mapper relies on it. diff --git a/eos/db/saveddata/cargo.py b/eos/db/saveddata/cargo.py index ce24dd48c7..b9b23adaf7 100644 --- a/eos/db/saveddata/cargo.py +++ b/eos/db/saveddata/cargo.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, DateTime from sqlalchemy.orm import mapper, relation -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.cargo import Cargo @@ -30,8 +30,8 @@ Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True), Column("itemID", Integer, nullable=False), Column("amount", Integer, nullable=False), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()), + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), ) mapper(Cargo, cargo_table, diff --git a/eos/db/saveddata/character.py b/eos/db/saveddata/character.py index 61c57b827c..4901216ef4 100644 --- a/eos/db/saveddata/character.py +++ b/eos/db/saveddata/character.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime from sqlalchemy.orm import relation, mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.db.saveddata.implant import charImplants_table @@ -38,8 +38,8 @@ Column("defaultLevel", Integer, nullable=True), Column("alphaCloneID", Integer, nullable=True), Column("ownerID", ForeignKey("users.ID"), nullable=True), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now())) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)) mapper(Character, characters_table, properties={ diff --git a/eos/db/saveddata/crest.py b/eos/db/saveddata/crest.py index fff78a3ee0..28f77a9836 100644 --- a/eos/db/saveddata/crest.py +++ b/eos/db/saveddata/crest.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, String, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.crestchar import CrestChar @@ -29,6 +29,6 @@ Column("name", String, nullable=False, unique=True), Column("refresh_token", String, nullable=False), # These records aren't updated. Instead, they are dropped and created, hence we don't have a modified field - Column("created", DateTime, nullable=True, default=func.now())) + Column("created", DateTime, nullable=True, default=datetime.datetime.now)) mapper(CrestChar, crest_table) diff --git a/eos/db/saveddata/damagePattern.py b/eos/db/saveddata/damagePattern.py index 6f20cd92f7..8a25367036 100644 --- a/eos/db/saveddata/damagePattern.py +++ b/eos/db/saveddata/damagePattern.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.damagePattern import DamagePattern @@ -32,8 +32,8 @@ Column("kineticAmount", Integer), Column("explosiveAmount", Integer), Column("ownerID", ForeignKey("users.ID"), nullable=True), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(DamagePattern, damagePatterns_table) diff --git a/eos/db/saveddata/drone.py b/eos/db/saveddata/drone.py index d59c15fbe2..b4c0cefb97 100644 --- a/eos/db/saveddata/drone.py +++ b/eos/db/saveddata/drone.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime from sqlalchemy.orm import mapper, relation -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.drone import Drone @@ -32,8 +32,8 @@ Column("amount", Integer, nullable=False), Column("amountActive", Integer, nullable=False), Column("projected", Boolean, default=False), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(Drone, drones_table, diff --git a/eos/db/saveddata/fighter.py b/eos/db/saveddata/fighter.py index e20e9fbdf6..bb1ed133fe 100644 --- a/eos/db/saveddata/fighter.py +++ b/eos/db/saveddata/fighter.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime from sqlalchemy.orm import mapper, relation -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.fighterAbility import FighterAbility @@ -33,8 +33,8 @@ Column("active", Boolean, nullable=True), Column("amount", Integer, nullable=False), Column("projected", Boolean, default=False), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) fighter_abilities_table = Table("fightersAbilities", saveddata_meta, diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 372d230f44..7f8fdbe616 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -22,7 +22,7 @@ from sqlalchemy.sql import and_ from sqlalchemy.orm import relation, reconstructor, mapper, relationship from sqlalchemy import ForeignKey, Column, Integer, String, Table, Boolean, DateTime -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.db import saveddata_session @@ -59,8 +59,8 @@ 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, default=func.now(), onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now) ) projectedFits_table = Table("projectedFits", saveddata_meta, @@ -68,16 +68,16 @@ Column("victimID", ForeignKey("fits.ID"), primary_key=True), Column("amount", Integer, nullable=False, default=1), Column("active", Boolean, nullable=False, default=1), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) commandFits_table = Table("commandFits", saveddata_meta, Column("boosterID", ForeignKey("fits.ID"), primary_key=True), Column("boostedID", ForeignKey("fits.ID"), primary_key=True), Column("active", Boolean, nullable=False, default=1), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) diff --git a/eos/db/saveddata/implant.py b/eos/db/saveddata/implant.py index e1f1632c4c..edf9aac12d 100644 --- a/eos/db/saveddata/implant.py +++ b/eos/db/saveddata/implant.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.implant import Implant @@ -28,8 +28,8 @@ Column("ID", Integer, primary_key=True), Column("itemID", Integer), Column("active", Boolean), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) fitImplants_table = Table("fitImplants", saveddata_meta, diff --git a/eos/db/saveddata/implantSet.py b/eos/db/saveddata/implantSet.py index c32700740c..369f047602 100644 --- a/eos/db/saveddata/implantSet.py +++ b/eos/db/saveddata/implantSet.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, String, DateTime from sqlalchemy.orm import relation, mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.db.saveddata.implant import implantsSetMap_table @@ -30,8 +30,8 @@ implant_set_table = Table("implantSets", saveddata_meta, Column("ID", Integer, primary_key=True), Column("name", String, nullable=False), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(ImplantSet, implant_set_table, diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py index fbbf9332a2..704eca99d4 100644 --- a/eos/db/saveddata/module.py +++ b/eos/db/saveddata/module.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime, select from sqlalchemy.orm import relation, mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.module import Module @@ -34,8 +34,8 @@ Column("state", Integer, CheckConstraint("state >= -1"), CheckConstraint("state <= 2")), Column("projected", Boolean, default=False, nullable=False), Column("position", Integer), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()), + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"')) mapper(Module, modules_table, diff --git a/eos/db/saveddata/override.py b/eos/db/saveddata/override.py index ed32884054..aa99c9763c 100644 --- a/eos/db/saveddata/override.py +++ b/eos/db/saveddata/override.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, Float, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.override import Override @@ -28,8 +28,8 @@ Column("itemID", Integer, primary_key=True, index=True), Column("attrID", Integer, primary_key=True, index=True), Column("value", Float, nullable=False), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(Override, overrides_table) diff --git a/eos/db/saveddata/skill.py b/eos/db/saveddata/skill.py index 40192def95..5e56f2cdbe 100644 --- a/eos/db/saveddata/skill.py +++ b/eos/db/saveddata/skill.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, ForeignKey, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.character import Skill @@ -29,8 +29,8 @@ Column("characterID", ForeignKey("characters.ID"), primary_key=True, index=True), Column("itemID", Integer, primary_key=True), Column("_Skill__level", Integer, nullable=True), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(Skill, skills_table) diff --git a/eos/db/saveddata/targetResists.py b/eos/db/saveddata/targetResists.py index bda30405d4..f100bc2ddd 100644 --- a/eos/db/saveddata/targetResists.py +++ b/eos/db/saveddata/targetResists.py @@ -19,7 +19,7 @@ from sqlalchemy import Table, Column, Integer, Float, ForeignKey, String, DateTime from sqlalchemy.orm import mapper -import sqlalchemy.sql.functions as func +import datetime from eos.db import saveddata_meta from eos.saveddata.targetResists import TargetResists @@ -32,8 +32,8 @@ Column("kineticAmount", Float), Column("explosiveAmount", Float), Column("ownerID", ForeignKey("users.ID"), nullable=True), - Column("created", DateTime, nullable=True, default=func.now()), - Column("modified", DateTime, nullable=True, onupdate=func.now()) + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now) ) mapper(TargetResists, targetResists_table)