Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merge

git-svn-id: http://projects.conceptive.be/camelot/svn/trunk@2560 8325946a-aa55-0410-8760-f0bc8c33efaa
  • Loading branch information...
commit 59d9d2f13742d456252af9fc009d92f208504517 1 parent 96ad0ac
erikj authored
Showing with 340 additions and 735 deletions.
  1. +37 −0 camelot/admin/action/form_action.py
  2. +19 −1 camelot/admin/application_admin.py
  3. +7 −1 camelot/admin/entity_admin.py
  4. +15 −0 camelot/admin/object_admin.py
  5. +25 −39 camelot/bin/meta.py
  6. +4 −2 camelot/core/conf.py
  7. +0 −356 camelot/view/action.py
  8. +1 −1  camelot/view/action_steps/change_object.py
  9. +2 −2 camelot/view/action_steps/gui.py
  10. +38 −22 camelot/view/controls/busy_widget.py
  11. +0 −95 camelot/view/controls/dashboard.py
  12. +6 −5 camelot/view/controls/editors/charteditor.py
  13. +10 −3 camelot/view/controls/editors/fileeditor.py
  14. +41 −51 camelot/view/controls/formview.py
  15. +0 −47 camelot/view/controls/statusbar.py
  16. +5 −4 camelot/view/controls/tableview.py
  17. +27 −31 camelot/view/forms.py
  18. +6 −12 camelot/view/mainwindow.py
  19. +1 −1  camelot/view/model_thread/__init__.py
  20. +1 −2  camelot/view/proxy/collection_proxy.py
  21. +2 −2 camelot_example/application_admin.py
  22. +5 −0 doc/sphinx/source/doc/application_admin.rst
  23. +1 −0  doc/sphinx/source/doc/index.rst
  24. +0 −1  doc/sphinx/source/doc/models.rst
  25. +55 −26 doc/sphinx/source/doc/under_the_hood.rst
  26. +2 −0  doc/sphinx/source/migrate/11-12-30.rst
  27. +3 −15 doc/sphinx/source/tutorial/videostore.rst
  28. +26 −14 readme.txt
  29. +1 −2  setup.py
View
37 camelot/admin/action/form_action.py
@@ -30,6 +30,7 @@
from application_action import ( ApplicationActionGuiContext,
ApplicationActionModelContext )
+import list_action
class FormActionModelContext( ApplicationActionModelContext ):
"""On top of the attributes of the
@@ -184,6 +185,42 @@ def model_run( self, model_context ):
yield action_steps.DeleteObject( obj )
admin.expunge( obj )
+class ToPreviousForm( list_action.ToPreviousRow ):
+ """Move to the previous form"""
+
+ def gui_run( self, gui_context ):
+ gui_context.view.to_previous()
+
+ def get_state( self, model_context ):
+ return Action.get_state( self, model_context )
+
+class ToFirstForm( list_action.ToFirstRow ):
+ """Move to the form"""
+
+ def gui_run( self, gui_context ):
+ gui_context.view.to_first()
+
+ def get_state( self, model_context ):
+ return Action.get_state( self, model_context )
+
+class ToNextForm( list_action.ToNextRow ):
+ """Move to the next form"""
+
+ def gui_run( self, gui_context ):
+ gui_context.view.to_next()
+
+ def get_state( self, model_context ):
+ return Action.get_state( self, model_context )
+
+class ToLastForm( list_action.ToLastRow ):
+ """Move to the last form"""
+
+ def gui_run( self, gui_context ):
+ gui_context.view.to_last()
+
+ def get_state( self, model_context ):
+ return Action.get_state( self, model_context )
+
def structure_to_form_actions( structure ):
"""Convert a list of python objects to a list of form actions. If the python
object is a tuple, a CallMethod is constructed with this tuple as arguments. If
View
20 camelot/admin/application_admin.py
@@ -31,7 +31,7 @@
from PyQt4.QtCore import Qt
from PyQt4 import QtCore, QtGui
-from camelot.admin.action import list_action, application_action
+from camelot.admin.action import application_action, form_action, list_action
from camelot.core.utils import ugettext_lazy as _
from camelot.view import art
from camelot.view import database_selection
@@ -135,6 +135,12 @@ class ApplicationAdmin(QtCore.QObject):
help_actions = [ application_action.ShowHelp(), ]
export_actions = [ list_action.PrintPreview(),
list_action.ExportSpreadsheet() ]
+ form_toolbar_actions = [ form_action.CloseForm(),
+ form_action.ToFirstForm(),
+ form_action.ToPreviousForm(),
+ form_action.ToNextForm(),
+ form_action.ToLastForm(),
+ application_action.Refresh() ]
def __init__(self):
"""Construct an ApplicationAdmin object and register it as the
@@ -302,6 +308,18 @@ def get_form_actions( self ):
"""
return []
+ def get_form_toolbar_actions( self, toolbar_area ):
+ """
+ :param toolbar_area: an instance of :class:`Qt.ToolBarArea` indicating
+ where the toolbar actions will be positioned
+
+ :return: a list of :class:`camelot.admin.action.base.Action` objects
+ that should be displayed on the toolbar of a form view. return
+ None if no toolbar should be created.
+ """
+ if toolbar_area == Qt.TopToolBarArea:
+ return self.form_toolbar_actions
+
def get_main_menu( self ):
"""
:return: a list of :class:`camelot.admin.menu.Menu` objects, or None if
View
8 camelot/admin/entity_admin.py
@@ -548,7 +548,13 @@ def flush(self, entity_instance):
from sqlalchemy.orm.session import Session
session = Session.object_session( entity_instance )
if session:
- modifications = self.get_modifications( entity_instance )
+ modifications = {}
+ try:
+ modifications = self.get_modifications( entity_instance )
+ except Exception, e:
+ # todo : there seems to be a bug in sqlalchemy that causes the
+ # get history to fail in some cases
+ logger.error( 'could not get modifications from object', exc_info = e )
session.flush( [entity_instance] )
#
# If needed, track the changes
View
15 camelot/admin/object_admin.py
@@ -353,6 +353,21 @@ def get_form_actions( self, obj ):
app_admin = self.get_application_admin()
from camelot.admin.action.form_action import structure_to_form_actions
return app_admin.get_form_actions() + structure_to_form_actions( self.form_actions )
+
+ @model_function
+ def get_form_toolbar_actions( self, toolbar_area ):
+ """
+ By default this function will return the same as :meth:`camelot.admin.application_admin.ApplicationAdmin.get_form_toolbar_actions`
+
+ :param toolbar_area: an instance of :class:`Qt.ToolBarArea` indicating
+ where the toolbar actions will be positioned
+
+ :return: a list of :class:`camelot.admin.action.base.Action` objects
+ that should be displayed on the toolbar of a form view. return
+ None if no toolbar should be created.
+ """
+ app_admin = self.get_application_admin()
+ return app_admin.get_form_toolbar_actions( toolbar_area )
def get_related_toolbar_actions( self, toolbar_area, direction ):
"""Specify the toolbar actions that should appear in a OneToMany editor.
View
64 camelot/bin/meta.py
@@ -168,8 +168,31 @@ class MyApplicationViewsTest( EntityViewsTest ):
('main.py', '''
import logging
-logging.basicConfig(level=logging.ERROR)
-logger = logging.getLogger('main')
+from camelot.core.conf import settings, SimpleSettings
+
+logging.basicConfig( level = logging.ERROR )
+logger = logging.getLogger( 'main' )
+
+# begin custom settings
+class MySettings( SimpleSettings ):
+
+ # add an ENGINE or a CAMELOT_MEDIA_ROOT method here to connect
+ # to another database or change the location where files are stored
+
+ def setup_model( self ):
+ """This function will be called at application startup, it is used to
+ setup the model"""
+ from camelot.core.sql import metadata
+ metadata.bind = self.ENGINE()
+ import camelot.model.authentication
+ import camelot.model.i18n
+ import camelot.model.memento
+ import {{options.module}}.model
+ metadata.create_all()
+
+my_settings = MySettings( '{{options.author}}', '{{options.name}}' )
+settings.append( my_settings )
+# end custom settings
def start_application():
from camelot.view.main import main
@@ -222,44 +245,7 @@ def start_application():
license
libs
'''),
- ('settings.py', '''
-import logging
-import os
-
-logger = logging.getLogger('settings')
-
-# media root needs to be an absolute path for the file open functions
-# to function correctly
-CAMELOT_MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'media')
-
-# backup root is the directory where the default backups are stored
-CAMELOT_BACKUP_ROOT = os.path.join(os.path.dirname(__file__), 'backup')
-
-# default extension for backup files
-CAMELOT_BACKUP_EXTENSION = 'db'
-
-# template used to create and find default backups
-CAMELOT_BACKUP_FILENAME_TEMPLATE = 'default-backup-%(text)s.' + CAMELOT_BACKUP_EXTENSION
-
-
-def ENGINE():
- """This function should return a database engine"""
- from sqlalchemy import create_engine
- return create_engine('sqlite:///model-data.sqlite')
-
-def setup_model():
- """This function will be called at application startup, it is used to setup
- the model"""
- from camelot.core.sql import metadata
- metadata.bind = ENGINE()
- import camelot.model.authentication
- import camelot.model.i18n
- import camelot.model.memento
- import {{options.module}}.model
- metadata.create_all()
- '''),
('setup.py', '''
-
#
# Default setup file for a Camelot application
#
View
6 camelot/core/conf.py
@@ -107,8 +107,10 @@ def __init__( self, author, name ):
:param author: the name of the writer of the application
:param name: the name of the application
- these name will be used to create a folder where the local data will
- be stored.
+ These names will be used to create a folder where the local data will
+ be stored. On Windows this will be in the AppData folder of the user,
+ otherwise it will be in a `.author` folder in the home directory of the
+ user.
"""
if ('win' in sys.platform) and ('darwin' not in sys.platform):
import winpaths
View
356 camelot/view/action.py
@@ -26,7 +26,6 @@
that can be invoked via menus, toolbar buttons, and keyboard shortcuts."""
from PyQt4 import QtGui
-from PyQt4.QtCore import Qt
from camelot.view.art import Icon
from camelot.core.utils import ugettext as _
@@ -39,19 +38,7 @@ class ActionFactory(object):
a default text, icon and shortcut.
"""
- icon_backup = Icon('tango/16x16/actions/document-save.png')
- icon_restore = Icon('tango/16x16/devices/drive-harddisk.png')
- icon_pgsetup = Icon('tango/16x16/actions/document-properties.png')
- icon_print = Icon('tango/16x16/actions/document-print.png')
- icon_preview = Icon('tango/16x16/actions/document-print-preview.png')
icon_copy = Icon('tango/16x16/actions/edit-copy.png')
- icon_new = Icon('tango/16x16/actions/document-new.png')
- icon_delete = Icon('tango/16x16/places/user-trash.png')
- icon_excel = Icon('tango/16x16/mimetypes/x-office-spreadsheet.png')
- icon_word = Icon('tango/16x16/mimetypes/x-office-document.png')
- icon_mail = Icon('tango/16x16/actions/mail-message-new.png')
- icon_import = Icon('tango/16x16/mimetypes/text-x-generic.png')
- icon_help = Icon('tango/16x16/apps/help-browser.png')
@classmethod
def create_action(*a, **kw):
@@ -109,346 +96,3 @@ def paste(cls, parent, slot, **kwargs):
)
default.update(kwargs)
return cls.create_action(**default)
-
- @classmethod
- def view_first(cls, parent, slot, **kwargs):
- default = dict(
- text=_('First'),
- slot=slot,
- parent=parent,
- shortcut=QtGui.QKeySequence.MoveToStartOfDocument,
- actionicon=Icon('tango/16x16/actions/go-first.png'),
- tip=_('First')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def view_last(cls, parent, slot, **kwargs):
- default = dict(
- text=_('Last'),
- slot=slot,
- parent=parent,
- shortcut=QtGui.QKeySequence.MoveToEndOfDocument,
- actionicon=Icon('tango/16x16/actions/go-last.png'),
- tip=_('Last')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def view_next(cls, parent, slot, **kwargs):
- default = dict(
- text=_('Next'),
- slot=slot,
- parent=parent,
- shortcut=QtGui.QKeySequence.MoveToNextPage,
- actionicon=Icon('tango/16x16/actions/go-next.png'),
- tip=_('Next')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def view_previous(cls, parent, slot, **kwargs):
- default = dict(
- text=_('Previous'),
- slot=slot,
- parent=parent,
- shortcut=QtGui.QKeySequence.MoveToPreviousPage,
- actionicon=Icon('tango/16x16/actions/go-previous.png'),
- tip=_('Previous')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def export_ooxml(cls, parent, slot, **kwargs):
- default = dict(
- text=_('To Word Processor'),
- slot=slot,
- parent=parent,
- actionicon=Icon('tango/16x16/mimetypes/x-office-document.png'),
- tip=_('Open using MS Word or Open Office')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def backup(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('&Backup'),
- slot=slot,
- actionicon=cls.icon_backup,
- tip=_('Backup the database')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def restore(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('&Restore'),
- slot=slot,
- actionicon=cls.icon_restore,
- tip=_('Restore the database from a backup')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def page_setup(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Page Setup...'),
- slot=slot,
- actionicon=cls.icon_pgsetup,
- tip=_('Page Setup...')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def print_(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Print...'),
- slot=slot,
- shortcut=QtGui.QKeySequence.Print,
- actionicon=cls.icon_print,
- tip=_('Print...')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def print_preview(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Print Preview'),
- slot=slot,
- actionicon=cls.icon_preview,
- tip=_('Print Preview')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def exit(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('E&xit'),
- slot=slot,
- shortcut=QtGui.QKeySequence.Quit,
- actionicon=Icon('tango/16x16/actions/system-shutdown.png'),
- tip=_('Exit the application')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def duplicate(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('&Copy'),
- slot=slot,
- shortcut=QtGui.QKeySequence.Copy,
- actionicon=cls.icon_copy,
- tip=_("Duplicate the selected rows")
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def select_all(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Select &All'),
- slot=slot,
- shortcut=QtGui.QKeySequence.SelectAll,
- tip=_('Select all rows in the table'),
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def about(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('&About'),
- slot=slot,
- actionicon=Icon('tango/16x16/mimetypes/application-certificate.png'),
- tip=_("Show the application's About box")
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def whats_new(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('&What\'s new'),
- slot=slot,
- actionicon=Icon('tango/16x16/status/software-update-available.png'),
- tip=_("Show the What's New box")
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def affiliated_website(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Affiliated website'),
- slot=slot,
- actionicon=Icon('tango/16x16/apps/internet-web-browser.png'),
- tip=_('Go to the affiliated website')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def remote_support(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Remote support'),
- slot=slot,
- actionicon=Icon('tango/16x16/devices/video-display.png'),
- tip=_('Let the support agent take over your desktop')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def help(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Help'),
- slot=slot,
- shortcut=QtGui.QKeySequence.HelpContents,
- actionicon=cls.icon_help,
- tip=_('Help content')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def new(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('New'),
- slot=slot,
- shortcut=QtGui.QKeySequence.New,
- actionicon=cls.icon_new,
- tip=_('New')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def delete(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Delete'),
- slot=slot,
- shortcut=QtGui.QKeySequence.Delete,
- actionicon=cls.icon_delete,
- tip=_('Delete')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def update_values(cls, parent, slot, **kwargs):
- default = dict(
- parent = parent,
- text = _('Replace field contents'),
- slot = slot,
- tip = _('Replace the content of a field for all rows in a selection')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def merge_document(cls, parent, slot, **kwargs):
- default = dict(
- parent = parent,
- text = _('Merge document'),
- slot = slot,
- tip = _('Merge a template document with all rows in a selection')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def export_excel(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Export to MS Excel'),
- slot=slot,
- actionicon=cls.icon_excel,
- tip=_('Export to MS Excel')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def export_word(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Export to MS Word'),
- slot=slot,
- actionicon=cls.icon_word,
- tip=_('Export to MS Word')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def export_mail(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Send by e-mail'),
- slot=slot,
- actionicon=cls.icon_mail,
- tip=_('Send by e-mail')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def import_file(cls, parent, slot, **kwargs):
- default = dict(
- parent=parent,
- text=_('Import from file'),
- slot=slot,
- actionicon=cls.icon_import,
- tip=_('Import from file')
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def refresh(cls, parent, slot, **kwargs):
- default = dict(
- parent = parent,
- text = _('Refresh'),
- slot = slot,
- shortcut = QtGui.QKeySequence( Qt.Key_F9 ),
- icond = Icon('tango/16x16/actions/view-refresh.png'),
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
- @classmethod
- def new_tab(cls, parent, slot, **kwargs):
- default = dict(
- parent = parent,
- text = _('Open in New Tab'),
- slot = slot
- )
- default.update(kwargs)
- return cls.create_action(**default)
-
View
2  camelot/view/action_steps/change_object.py
@@ -299,7 +299,7 @@ def filter(attributes):
choices = [(field, attributes['name']) for field, attributes in field_attributes.items() if filter(attributes)]
choices.sort( key = lambda choice:choice[1] )
editor.set_choices( choices )
- editor.set_value( ( choices+[(None,None)] )[1][0] )
+ editor.set_value( ( choices+[(None,None)] )[-1][0] )
self.field_changed( 0 )
editor.currentIndexChanged.connect( self.field_changed )
View
4 camelot/view/action_steps/gui.py
@@ -76,8 +76,8 @@ def gui_run( self, gui_context ):
class ShowChart( ActionStep ):
"""Show a full screen chart.
- :param chart: a :class:`camelot.core.container.FigureContainer` or
- :class:`camelot.core.container.AxesContainer`
+ :param chart: a :class:`camelot.container.chartcontainer.FigureContainer` or
+ :class:`camelot.container.chartcontainer.AxesContainer`
"""
def __init__( self, chart ):
View
60 camelot/view/controls/busy_widget.py
@@ -25,13 +25,15 @@
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt
-class BusyWidget(QtGui.QWidget):
+from camelot.view.model_thread import get_model_thread
+
+class BusyWidget( QtGui.QLabel ):
"""A widget indicating the application is performing some background task.
The widget acts as an overlay of its parent widget and displays animating
orbs"""
def __init__(self, parent = None):
- QtGui.QWidget.__init__(self, parent)
+ super( BusyWidget, self ).__init__( 'foo', parent )
palette = QtGui.QPalette(self.palette())
palette.setColor(palette.Background, Qt.transparent)
self.setPalette(palette)
@@ -39,6 +41,17 @@ def __init__(self, parent = None):
self.orbs = 5
self.highlighted_orb = self.orbs
self.timer = None
+ #
+ # self.busy is kept, because show and hide don't work for widgets
+ # in a QToolbar. todo : implement show and hide through the QAction
+ # of the toolbar widget, to save on paint resources
+ #
+ self.busy = False
+ self.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding )
+ mt = get_model_thread()
+ mt.thread_busy_signal.connect( self.set_busy )
+ # the model thread might already be busy before we connected to it
+ self.set_busy( mt.busy() )
@QtCore.pyqtSlot(bool)
def set_busy(self, busy_state):
@@ -51,6 +64,7 @@ def set_busy(self, busy_state):
# so a check on self.timer is needed to prevent multiple timers
# from being started
#
+ self.busy = busy_state
if busy_state and self.timer==None:
self.timer = self.startTimer(200)
self.counter = 0
@@ -60,28 +74,30 @@ def set_busy(self, busy_state):
self.killTimer(self.timer)
self.timer = None
self.hide()
-
+ self.update()
+
def paintEvent(self, event):
"""custom paint, painting the orbs"""
- painter = QtGui.QPainter()
- painter.begin(self)
- painter.setRenderHint(QtGui.QPainter.Antialiasing)
- painter.setPen(QtGui.QPen(Qt.NoPen))
- width = self.width()
- height = self.height()
- radius = min(width/(3*self.orbs+1), height/4)
- for i in range(self.orbs):
- if i!=self.highlighted_orb:
- painter.setBrush(QtGui.QBrush(QtGui.QColor(180, 180, 180)))
- else:
- painter.setBrush(QtGui.QBrush(QtGui.QColor(127, 127, 127)))
- center_x = width - (3*i+2)*radius
- center_y = height / 2
- painter.drawEllipse(center_x - radius,
- center_y - radius,
- 2*radius,
- 2*radius)
- painter.end()
+ if self.busy:
+ painter = QtGui.QPainter()
+ painter.begin(self)
+ painter.setRenderHint(QtGui.QPainter.Antialiasing)
+ painter.setPen(QtGui.QPen(Qt.NoPen))
+ width = self.width()
+ height = self.height()
+ radius = min( width/(3*self.orbs+1), height/4, 4 )
+ for i in range(self.orbs):
+ if i!=self.highlighted_orb:
+ painter.setBrush(QtGui.QBrush(QtGui.QColor(180, 180, 180)))
+ else:
+ painter.setBrush(QtGui.QBrush(QtGui.QColor(127, 127, 127)))
+ center_x = width - (3*i+2)*radius
+ center_y = height / 2
+ painter.drawEllipse(center_x - radius,
+ center_y - radius,
+ 2*radius,
+ 2*radius)
+ painter.end()
def timerEvent(self, event):
"""custom timer event, updating the animation"""
View
95 camelot/view/controls/dashboard.py
@@ -1,95 +0,0 @@
-# ============================================================================
-#
-# Copyright (C) 2007-2012 Conceptive Engineering bvba. All rights reserved.
-# www.conceptive.be / project-camelot@conceptive.be
-#
-# This file is part of the Camelot Library.
-#
-# This file may be used under the terms of the GNU General Public
-# License version 2.0 as published by the Free Software Foundation
-# and appearing in the file license.txt included in the packaging of
-# this file. Please review this information to ensure GNU
-# General Public Licensing requirements will be met.
-#
-# If you are unsure which license is appropriate for your use, please
-# visit www.python-camelot.com or contact project-camelot@conceptive.be
-#
-# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
-# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-#
-# For use of this library in commercial applications, please contact
-# project-camelot@conceptive.be
-#
-# ============================================================================
-from PyQt4.QtGui import QApplication, QFrame, QPalette, QLabel, QPixmap
-from PyQt4.QtCore import Qt, QRect, QCoreApplication
-
-
-class BareFrame(QFrame):
-
- def __init__(self, parent=None):
- super(BareFrame, self).__init__(parent)
- self.setWindowFlags(Qt.FramelessWindowHint)
- self.setFrameShadow(QFrame.Plain)
- self.setFrameShape(QFrame.Box)
- self.setLineWidth(1)
-
- def setBGColor(self, color):
- pal = QCoreApplication.instance().palette()
- pal.setColor(QPalette.Window, color)
- self.setPalette(pal)
-
-
-class CloseMark(QLabel):
-
- WIDTH = 31
- HEIGHT = 31
- MARGIN = 10
-
- def __init__(self, pixmap, parent=None):
- super(CloseMark, self).__init__(parent)
- self.setPixmap(pixmap)
- self.toParentTopRight()
-
- def mousePressEvent(self, event):
- self.parent().close()
-
- def toParentTopRight(self):
- parent = self.parent()
- x = parent.width() - CloseMark.MARGIN - CloseMark.WIDTH
- y = CloseMark.MARGIN
- w = CloseMark.WIDTH
- h = CloseMark.HEIGHT
- self.setGeometry(QRect(x, y, w, h))
-
-
-class Dashboard(BareFrame):
-
- SCALE = .85
-
- def __init__(self, parent=None):
- from camelot.view.controls.busy_widget import BusyWidget
- from camelot.view.model_thread import get_model_thread
- super(Dashboard, self).__init__(parent)
- desktop = QCoreApplication.instance().desktop()
- self.resize(desktop.width() * Dashboard.SCALE, desktop.height() * Dashboard.SCALE)
- self.closemark = CloseMark(QPixmap('close-mark.png'), self)
- self.setBGColor(Qt.white)
- busy_widget = BusyWidget(self)
- busy_widget.setMinimumSize( desktop.width() * Dashboard.SCALE, desktop.height() * Dashboard.SCALE )
- #self.addPermanentWidget(busy_widget, 0)
- mt = get_model_thread()
- mt.thread_busy_signal.connect( busy_widget.set_busy )
- busy_widget.set_busy(mt.busy())
-
-
-
-if __name__ == '__main__':
- import sys
- app = QApplication(sys.argv)
- board = Dashboard()
- board.show()
- sys.exit(app.exec_())
-
-
-
View
11 camelot/view/controls/editors/charteditor.py
@@ -27,7 +27,7 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
-from camelot.view.controls.editors.customeditor import CustomEditor
+from camelot.view.controls.editors.customeditor import AbstractCustomEditor
from camelot.view.controls.editors.wideeditor import WideEditor
from camelot.view.proxy import ValueLoading
from camelot.view.art import Icon
@@ -37,7 +37,7 @@
LOGGER = logging.getLogger('camelot.view.controls.editors.charteditor')
-class ChartEditor(QtGui.QFrame, CustomEditor, WideEditor):
+class ChartEditor(QtGui.QFrame, AbstractCustomEditor, WideEditor):
"""Editor to display and manipulate matplotlib charts. The editor
itself is generic for all kinds of plots, it simply provides the
data to be ploted with a set of axes. The data itself should know
@@ -50,7 +50,8 @@ class ChartEditor(QtGui.QFrame, CustomEditor, WideEditor):
def __init__(self, parent=None, width=50, height=40, dpi=50, field_name='chart', **kwargs):
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
- super(ChartEditor, self).__init__(parent)
+ super(ChartEditor, self).__init__( parent )
+ AbstractCustomEditor.__init__( self )
self.setObjectName( field_name )
chart_frame = QtGui.QFrame( self )
@@ -185,11 +186,11 @@ def set_value(self, value):
"""Accepts a camelot.container.chartcontainer.FigureContainer or a
camelot.container.chartcontainer.AxesContainer """
from camelot.container.chartcontainer import structure_to_figure_container
- self._value = structure_to_figure_container( super(ChartEditor, self).set_value(value) )
+ self._value = structure_to_figure_container( AbstractCustomEditor.set_value( self, value ) )
self.on_draw()
def get_value(self):
- super(ChartEditor, self).get_value() or self._value
+ AbstractCustomEditor.get_value( self ) or self._value
# def _get_tightbbox(self, fig, pad_inches):
# renderer = fig.canvas.get_renderer()
View
13 camelot/view/controls/editors/fileeditor.py
@@ -206,14 +206,21 @@ def add_button_clicked(self):
self.stored_file_ready,
filter=self.filter,
remove_original=self.remove_original,
- )
+ )
+
def open_button_clicked(self):
from camelot.view.storage import open_stored_file
open_stored_file(self, self.value)
def clear_button_clicked(self):
- self.value = None
- self.editingFinished.emit()
+ answer = QtGui.QMessageBox.question( self,
+ _('Remove this file ?'),
+ _('If you continue, you will no longer be able to open this file.'),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No )
+ if answer == QtGui.QMessageBox.Yes:
+ self.value = None
+ self.editingFinished.emit()
#
# Drag & Drop
View
92 camelot/view/controls/formview.py
@@ -24,7 +24,9 @@
"""form view"""
+import functools
import logging
+
LOGGER = logging.getLogger('camelot.view.controls.formview')
from PyQt4 import QtGui
@@ -33,29 +35,10 @@
from camelot.admin.action.application_action import Refresh
from camelot.admin.action.form_action import FormActionGuiContext
-from camelot.view.art import Icon
from camelot.view.model_thread import post
from camelot.view.controls.view import AbstractView
-from camelot.view.controls.statusbar import StatusBar
+from camelot.view.controls.busy_widget import BusyWidget
from camelot.view import register
-from camelot.view.action import ActionFactory
-
-class ContextMenuAction(QtGui.QAction):
-
- default_icon = Icon('tango/16x16/categories/applications-system.png')
-
- def __init__(self, parent, title, icon = None):
- """
- :param parent: the widget on which the context menu will be placed
- :param title: text displayed in the context menu
- :param icon: camelot.view.art.Icon object
- """
- super(ContextMenuAction, self).__init__(title, parent)
- self.icon = icon
- if self.icon:
- self.setIcon(self.icon.getQIcon())
- else:
- self.setIcon(self.default_icon.getQIcon())
class FormEditors( object ):
"""A class that holds the editors used on a form
@@ -282,6 +265,9 @@ def __init__(self, title, admin, model, index, parent = None):
AbstractView.__init__( self, parent )
layout = QtGui.QVBoxLayout()
+ layout.setSpacing( 1 )
+ layout.setMargin( 1 )
+ layout.setObjectName( 'layout' )
form_and_actions_layout = QtGui.QHBoxLayout()
form_and_actions_layout.setObjectName('form_and_actions_layout')
layout.addLayout( form_and_actions_layout )
@@ -304,13 +290,7 @@ def __init__(self, title, admin, model, index, parent = None):
self.gui_context.view = self
self.gui_context.widget_mapper = self.findChild( QtGui.QDataWidgetMapper,
'widget_mapper' )
-
- statusbar = StatusBar(self)
- statusbar.setObjectName('statusbar')
- statusbar.setSizeGripEnabled(False)
- layout.addWidget(statusbar)
- layout.setAlignment(statusbar, Qt.AlignBottom)
- self.setLayout(layout)
+ self.setLayout( layout )
self.change_title(title)
@@ -319,23 +299,17 @@ def __init__(self, title, admin, model, index, parent = None):
self.accept_close_event = False
- def get_actions():
- return admin.get_form_actions(None)
-
- post(get_actions, self.setActions)
- #
- # Define actions
- #
- self.setContextMenuPolicy(Qt.ActionsContextMenu)
- self.addAction( ActionFactory.view_first(self, self.viewFirst) )
- self.addAction( ActionFactory.view_last(self, self.viewLast) )
- self.addAction( ActionFactory.view_next(self, self.viewNext) )
- self.addAction( ActionFactory.view_previous(self, self.viewPrevious) )
- self.addAction( ActionFactory.refresh(self, self.session_refresh ) )
-
- @QtCore.pyqtSlot()
- def session_refresh(self):
- self.refresh_action.gui_run( self.gui_context )
+ get_actions = admin.get_form_actions
+ post( functools.update_wrapper( functools.partial( get_actions,
+ None ),
+ get_actions ),
+ self.set_actions )
+
+ get_toolbar_actions = admin.get_form_toolbar_actions
+ post( functools.update_wrapper( functools.partial( get_toolbar_actions,
+ Qt.TopToolBarArea ),
+ get_toolbar_actions ),
+ self.set_toolbar_actions )
@QtCore.pyqtSlot()
def refresh(self):
@@ -354,7 +328,7 @@ def update_title(self, current_index ):
post( self._get_title, self.change_title, args=(current_index,) )
@QtCore.pyqtSlot(list)
- def setActions(self, actions):
+ def set_actions(self, actions):
form = self.findChild(QtGui.QWidget, 'form' )
layout = self.findChild(QtGui.QLayout, 'form_and_actions_layout' )
if actions and form and layout:
@@ -368,15 +342,32 @@ def setActions(self, actions):
side_panel_layout.addWidget( actions_widget )
side_panel_layout.addStretch()
layout.addLayout( side_panel_layout )
-
- def viewFirst(self):
+
+ @QtCore.pyqtSlot(list)
+ def set_toolbar_actions(self, actions):
+ layout = self.findChild( QtGui.QLayout, 'layout' )
+ if layout and actions:
+ toolbar = QtGui.QToolBar()
+ for action in actions:
+ qaction = action.render( self.gui_context, toolbar )
+ qaction.triggered.connect( self.action_triggered )
+ toolbar.addAction( qaction )
+ toolbar.addWidget( BusyWidget() )
+ layout.insertWidget( 0, toolbar )
+
+ @QtCore.pyqtSlot( bool )
+ def action_triggered( self, _checked = False ):
+ action_action = self.sender()
+ action_action.action.gui_run( self.gui_context )
+
+ def to_first(self):
"""select model's first row"""
form = self.findChild(QtGui.QWidget, 'form' )
if form:
form.submit()
form.to_first()
- def viewLast(self):
+ def to_last(self):
"""select model's last row"""
# submit should not happen a second time, since then we don't want
# the widgets data to be written to the model
@@ -385,7 +376,7 @@ def viewLast(self):
form.submit()
form.to_last()
- def viewNext(self):
+ def to_next(self):
"""select model's next row"""
# submit should not happen a second time, since then we don't want
# the widgets data to be written to the model
@@ -394,7 +385,7 @@ def viewNext(self):
form.submit()
form.to_next()
- def viewPrevious(self):
+ def to_previous(self):
"""select model's previous row"""
# submit should not happen a second time, since then we don't want
# the widgets data to be written to the model
@@ -425,4 +416,3 @@ def closeEvent(self, event):
# is processed
QtCore.QTimer.singleShot( 0, self.validate_close )
event.ignore()
-
View
47 camelot/view/controls/statusbar.py
@@ -1,47 +0,0 @@
-# ============================================================================
-#
-# Copyright (C) 2007-2012 Conceptive Engineering bvba. All rights reserved.
-# www.conceptive.be / project-camelot@conceptive.be
-#
-# This file is part of the Camelot Library.
-#
-# This file may be used under the terms of the GNU General Public
-# License version 2.0 as published by the Free Software Foundation
-# and appearing in the file license.txt included in the packaging of
-# this file. Please review this information to ensure GNU
-# General Public Licensing requirements will be met.
-#
-# If you are unsure which license is appropriate for your use, please
-# visit www.python-camelot.com or contact project-camelot@conceptive.be
-#
-# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
-# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-#
-# For use of this library in commercial applications, please contact
-# project-camelot@conceptive.be
-#
-# ============================================================================
-
-"""A custom status bar containing a progress indicator"""
-
-import logging
-logger = logging.getLogger('camelot.view.controls.statusbar')
-
-from PyQt4 import QtGui
-from camelot.view.model_thread import get_model_thread
-
-class StatusBar(QtGui.QStatusBar):
-
- def __init__(self, parent):
- QtGui.QStatusBar.__init__(self, parent)
- from camelot.view.controls.busy_widget import BusyWidget
- self.busy_widget = BusyWidget(self)
- self.busy_widget.setMinimumWidth(100)
- self.addPermanentWidget(self.busy_widget, 0)
- mt = get_model_thread()
- mt.thread_busy_signal.connect( self.busy_widget.set_busy )
- # the model thread might already be busy before we connected to it
- self.busy_widget.set_busy(mt.busy())
-
-
-
View
9 camelot/view/controls/tableview.py
@@ -495,11 +495,11 @@ def __init__( self, parent, admin ):
widget_layout.addWidget( self.number_of_rows )
else:
self.number_of_rows = None
- layout.addLayout( widget_layout )
+ layout.addLayout( widget_layout, 0 )
self._expanded_filters_created = False
self._expanded_search = QtGui.QWidget()
self._expanded_search.hide()
- layout.addWidget(self._expanded_search)
+ layout.addWidget( self._expanded_search, 1 )
self.setLayout( layout )
self.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed )
self.setNumberOfRows( 0 )
@@ -512,7 +512,8 @@ def _fill_expanded_search_options(self, columns):
"""
assert object_thread( self )
from camelot.view.controls.filter_operator import FilterOperator
- layout = QtGui.QHBoxLayout()
+ from camelot.view.flowlayout import FlowLayout
+ layout = FlowLayout()
layout.setSpacing( 2 )
layout.setContentsMargins( 0, 0, 0, 0 )
for i, (field, attributes) in enumerate(columns):
@@ -527,7 +528,7 @@ def _fill_expanded_search_options(self, columns):
box.setLayout( box_layout )
widget.filter_changed_signal.connect( self._filter_changed )
layout.addWidget( box )
- layout.addStretch()
+ #layout.addStretch()
self._expanded_search.setLayout( layout )
self._expanded_filters_created = True
View
58 camelot/view/forms.py
@@ -40,31 +40,24 @@ class Form( list ):
sub-forms. A form can thus be manipulated using the list's method such as
append or insert.
-A form can be converted to a
-QT widget by calling its render method. The base form uses the QFormLayout
-to render a form::
+A form can be converted to a `Qt` widget by calling its `render` method.
- class Admin(EntityAdmin):
- form_display = Form(['title', 'short_description', 'director', 'release_date'])
+Forms are defined using the `form_display` attribute of an `Admin` class::
-..image:: /_static/form/form.png
-"""
+ class Admin( EntityAdmin ):
+ form_display = Form( [ 'title', 'short_description',
+ 'release_date' ] )
+
+and takes these parameters :
- def __init__( self, content, scrollbars = False, columns = 1 ):
- """
- :param content: an iterable with the field names and forms to render
- :param columns: the number of columns in which to order the fields.
+ :param content: an iterable with field names or sub-forms to render
+ :param columns: the number of columns in which to order the fields.
- eg : with 2 columns, the fields ['street', 'city', 'country'] will
- be ordered as :
+.. image:: /_static/form/form.png
- +-------------+--------------+
- | street | city |
- +-------------+--------------+
- | country | |
- +-------------+--------------+
+"""
- """
+ def __init__( self, content, scrollbars = False, columns = 1 ):
super(Form, self).__init__( content )
self._scrollbars = scrollbars
self._columns = columns
@@ -88,7 +81,7 @@ def remove_field( self, original_field ):
inherited forms.
:param original_field: the name of the field to be removed
- :return: True if the field was found and removed
+ :return: `True` if the field was found and removed
"""
for c in self:
if isinstance( c, Form ):
@@ -104,7 +97,7 @@ def replace_field( self, original_field, new_field ):
:param original_field : the name of the field to be replace
:param new_field : the name of the new field
- :return: True if the original field was found and replaced.
+ :return: `True` if the original field was found and replaced
"""
for i, c in enumerate( self ):
if isinstance( c, Form ):
@@ -130,7 +123,7 @@ def render( self, widgets, parent = None, toplevel = False):
while a non toplevel form is only expanding if it contains other
expanding elements.
- :return: a QWidget into which the form is rendered
+ :return: a :class:`QtGui.QWidget` into which the form is rendered
"""
logger.debug( 'rendering %s' % (self.__class__.__name__) )
from camelot.view.controls.editors.wideeditor import WideEditor
@@ -242,7 +235,7 @@ def __str__(self):
return form_widget
class Label( Form ):
- """Render a label with a QLabel"""
+ """Render a label using a :class:`QtGui.QLabel`"""
def __init__( self, label, alignment='left', style=None):
"""
@@ -333,7 +326,7 @@ def render_tab( self, index ):
class TabForm( Form ):
"""
-Render forms within a QTabWidget::
+Render forms within a :class:`QtGui.QTabWidget`::
from = TabForm([('First tab', ['title', 'short_description']),
('Second tab', ['director', 'release_date'])])
@@ -522,7 +515,7 @@ def __init__(self, field=None, num=2):
class GridForm( Form ):
"""Put different fields into a grid, without a label. Row or column labels can be added
- using the Label form::
+ using the :class:`Label` form::
GridForm([['title', 'short_description'], ['director','release_date']])
@@ -563,16 +556,22 @@ def render( self, widgets, parent = None, toplevel = False ):
skip = 0
for j, field in enumerate( row ):
num = 1
+ col = j + skip
if isinstance( field, ColumnSpan ):
num = field.num
field = field.field
-
if isinstance( field, Form ):
- grid_layout.addWidget( field.render( widgets, parent ), i, j + skip, 1, num )
+ form = field.render( widgets, parent )
+ if isinstance( form, QtGui.QWidget ):
+ grid_layout.addWidget( form, i, col, 1, num )
+ elif isinstance( form, QtGui.QLayoutItem ):
+ grid_layout.addItem( form, i, col, 1, num )
+ elif isinstance( form, QtGui.QLayout ):
+ grid_layout.addLayout( form, i, col, 1, num )
skip += num - 1
else:
editor = widgets.create_editor( field, widget )
- grid_layout.addWidget( editor, i, j + skip, 1, num )
+ grid_layout.addWidget( editor, i, col, 1, num )
skip += num - 1
widget.setLayout( grid_layout )
@@ -644,6 +643,3 @@ def structure_to_form( structure ):
if isinstance( structure, Form ):
return structure
return Form( structure )
-
-
-
View
18 camelot/view/mainwindow.py
@@ -28,6 +28,7 @@
from PyQt4.QtCore import Qt
from PyQt4 import QtGui, QtCore
+from camelot.view.controls.busy_widget import BusyWidget
from camelot.view.controls.navpane2 import NavigationPane
from camelot.view.model_thread import post
@@ -89,9 +90,6 @@ def __init__(self, gui_context, parent=None):
self.set_bottom_toolbar_actions,
args = (Qt.BottomToolBarArea,) )
- logger.debug('creating status bar')
- self.create_status_bar()
-
logger.debug('reading saved settings')
self.read_settings()
@@ -211,6 +209,7 @@ def set_toolbar_actions( self, toolbar_area, toolbar_actions ):
qaction.triggered.connect( self.action_triggered )
toolbar.addAction( qaction )
self.toolbars.append( toolbar )
+ toolbar.addWidget( BusyWidget() )
@QtCore.pyqtSlot( object )
def set_left_toolbar_actions( self, toolbar_actions ):
@@ -237,9 +236,10 @@ def view_activated( self ):
model_context = gui_context.create_model_context()
for toolbar in self.toolbars:
for qaction in toolbar.actions():
- post( qaction.action.get_state,
- qaction.set_state,
- args = ( model_context, ) )
+ if isinstance( qaction, ActionAction ):
+ post( qaction.action.get_state,
+ qaction.set_state,
+ args = ( model_context, ) )
menu_bar = self.menuBar()
if menu_bar:
for qaction in menu_bar.findChildren( ActionAction ):
@@ -272,12 +272,6 @@ def set_sections( self, sections ):
else:
self.navpane = None
- def create_status_bar( self ):
- from controls.statusbar import StatusBar
- statusbar = StatusBar( self )
- self.setStatusBar( statusbar )
- statusbar.showMessage( _('Ready'), 5000 )
-
def closeEvent( self, event ):
from camelot.view.model_thread import get_model_thread
model_thread = get_model_thread()
View
2  camelot/view/model_thread/__init__.py
@@ -55,7 +55,7 @@ def do_something( self ):
documentation.
"""
return self.thread() == QtCore.QThread.currentThread()
-
+
def model_function(original_function):
"""Decorator to ensure a function is only called from within the model
thread. If this function is called in another thread, an exception will be
View
3  camelot/view/proxy/collection_proxy.py
@@ -320,7 +320,6 @@ def parent( self, child ):
return QtCore.QModelIndex()
def rowCount( self, index = None ):
- assert object_thread( self )
return self._rows
def hasChildren( self, parent ):
@@ -436,7 +435,7 @@ def handle_entity_update( self, sender, entity ):
( self.__class__.__name__, self.admin.get_verbose_name() ) )
if sender != self:
try:
- row = self.display_cache.get_row_by_entity(entity)
+ row = self.display_cache.get_row_by_entity( entity )
except KeyError:
self.logger.debug( 'entity not in cache' )
return
View
4 camelot_example/application_admin.py
@@ -69,7 +69,7 @@ class MiniApplicationAdmin( MyApplicationAdmin ):
def get_toolbar_actions( self, toolbar_area ):
from PyQt4.QtCore import Qt
- from camelot.model.authentication import Person
+ from camelot.model.party import Person
from camelot.admin.action import application_action, list_action
from model import Movie
@@ -99,4 +99,4 @@ def get_stylesheet(self):
from camelot.view import art
return art.read('stylesheet/black.qss')
-# end mini admin
+# end mini admin
View
5 doc/sphinx/source/doc/application_admin.rst
@@ -51,8 +51,13 @@ Default behavior of the application
-----------------------------------
* :meth:`camelot.admin.application_admin.ApplicationAdmin.get_related_admin`
+
+The look of the form views
+--------------------------
+
* :meth:`camelot.admin.application_admin.ApplicationAdmin.get_related_toolbar_actions`
* :meth:`camelot.admin.application_admin.ApplicationAdmin.get_form_actions`
+ * :meth:`camelot.admin.application_admin.ApplicationAdmin.get_form_toolbar_actions`
Example
-------
View
1  doc/sphinx/source/doc/index.rst
@@ -25,6 +25,7 @@ All other sections can be read on an as needed base.
delegates.rst
charts.rst
documents.rst
+ under_the_hood.rst
data_model.rst
fixtures.rst
manage.rst
View
1  doc/sphinx/source/doc/models.rst
@@ -62,4 +62,3 @@ can be used as a place to start the model definition.
fields.rst
calculated_fields.rst
views.rst
- under_the_hood.rst
View
81 doc/sphinx/source/doc/under_the_hood.rst
@@ -7,19 +7,47 @@ Under the hood
A lot of things happen when a Camelot application starts up.
In this section we give a brief overview of those which might need to be adapted for more complex applications
+.. _settings:
+
+Global settings
+===============
+
+Camelot has a global `settings` object of which the attributes are used throughout Camelot whenever a piece
+of global configuration is needed.
+Examples of such global configuration are the location of the database and the location of stored files and
+images.
+To access the global configuration, simply import the object ::
+
+ from camelot.core.conf import settings
+ print settings.CAMELOT_MEDIA_ROOT()
+
+To manipulate the global configuration, create a class with the needed attributes and methods and append
+it to the global configuration :
+
+.. literalinclude:: ../../../../new_project/main.py
+ :start-after: begin custom settings
+ :end-before: end custom settings
+
+The `settings` object should have a method named ``ENGINE``, uses the `create_engine <http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine>`_
+SQLAlchemy function to create a connection to the database.
+Camelot provides a default ``sqlite`` URI scheme. But you can set your own.
+
+
+
+Older versions of Camelot looked for a `settings` module on `sys.path` to look for the global configuration.
+This approach is still supported.
+
Setting up the ORM
==================
-When the application starts up, the `setup_model` function in the `settings.py` file
-is called. In this function, all model files should be imported, to make sure the
-model has been completely setup. The importing of these files is enough to define
-the mapping between objects and tables.
+When the application starts up, the `setup_model` method of the `Settings` class is called.
+In this function, all model files should be imported, to make sure the model has been completely setup.
+The importing of these files is enough to define the mapping between objects and tables.
-.. literalinclude:: ../../../../new_project/settings.py
- :pyobject: ENGINE
+.. literalinclude:: ../../../../new_project/main.py
+ :pyobject: MySettings.setup_model
-The import of these model definitions should happen before the call to `create_all` to
-make sure all models are known before the tables are created.
+The import of these model definitions should happen before the call to `create_all` to make sure all models are known before the tables are created.
Setting up the Database
=======================
@@ -27,11 +55,13 @@ Setting up the Database
Engine
------
-The :file:`settings.py` file should contain a function named ENGINE that returns a
-connection to the database. Whenever a connection to the database is needed, this function will be called.
+The `Settings` class should contain a method named `ENGINE` that returns a connection to the database.
+Whenever a connection to the database is needed, this method will be called.
+The :class:`camelot.core.conf.SimpleSettings` has a default `ENGINE` method that returns an SQLite
+database in a user directory.
-.. literalinclude:: ../../../../new_project/settings.py
- :pyobject: ENGINE
+.. literalinclude:: ../../../../camelot/core/conf/settings.py
+ :pyobject: SimpleSettings.ENGINE
Metadata
--------
@@ -42,8 +72,8 @@ default :class:`MetaData` object used by Camelot.
In the `setup_model` function, this `metadata` object is bound to the database engine.
-.. literalinclude:: ../../../../new_project/settings.py
- :pyobject: setup_model
+.. literalinclude:: ../../../../new_project/main.py
+ :pyobject: MySettings
In case an application works with multiple database schemas in parallel, this step needs to be adapted.
@@ -54,16 +84,16 @@ By simply importing the modules which contain parts of the model definition, the
is added to the `metadata` object. At the end of the `setup_model` function, the `create_all` method is called on the metadata, which
will create the tables in the database if they don't exist yet.
-.. literalinclude:: ../../../../new_project/settings.py
- :pyobject: setup_model
+.. literalinclude:: ../../../../new_project/main.py
+ :pyobject: MySettings
Working without the default model
=================================
Camelot comes with a default model for Persons, Organizations, History tracking, etc.
-To turn this off, simply remove the import statements of those modules from the
-`setup_model` method in `settings.py`.
+To turn these on or off, simply add or remove the import statements of those modules from the
+`setup_model` method in the `Settings` class.
Transactions
============
@@ -91,19 +121,18 @@ Using Camelot without the GUI
Often a Camelot application also has a non GUI part, like batch scripts, server side
scripts, etc.
-It is of course perfectly possible to reuse the whole model definition in those non
-GUI parts. The easiest way to do so is to leave the Camelot GUI application as it
-is and then in the non GUI script, initialize the model first ::
+It is of course perfectly possible to reuse the whole model definition in those non GUI parts.
+The easiest way to do so is to leave the Camelot GUI application as it is and then in the non GUI script, initialize the model first ::
- import settings
+ from camelot.core.conf import settings
settings.setup_model()
-From that point, all model manipulations can be done. Access to the session can
-be obtained via any Entity subclass, such as Person ::
+From that point, all model manipulations can be done. Access to the single
+session can be obtained from anywhere through the `Session` factory method ::
- session = Person.query.session
+ from camelot.core.orm import Session
+ session = Session()
After the manipulations to the model have been done, they can be flushed to the db ::
session.flush()
-
View
2  doc/sphinx/source/migrate/11-12-30.rst
@@ -146,3 +146,5 @@ history information ::
DROP TABLE memento;
DROP TABLE authentication_mechanism_username;
DROP TABLE authentication_mechanism;
+
+Consider converting your `settings.py` module to a :ref:`settings object<settings>` .
View
18 doc/sphinx/source/tutorial/videostore.rst
@@ -36,7 +36,6 @@ Main Window and Views
one called :file:`main.py` which contains the entry point of your Camelot
application. If you launch it::
- set PYTHONPATH=.
python main.py
your `PyQt <http://www.riverbankcomputing.co.uk/software/pyqt/intro>`_
@@ -99,20 +98,9 @@ database model.
Creating the Movie Model
========================
-Let's first take a look at the :file:`settings.py` in our project directory.
-There is a function, ``ENGINE``, which returns a call to the `create_engine
-<http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine>`_
-SQLAlchemy function, containing a :abbr:`Uniform Resource Identifier URI`.
-That's the database your Camelot application will be connecting too.
-Camelot provides a default ``sqlite`` URI scheme. But you can set your own.
-
-If you set a database file that does not exist it will be created in the
-directory from which the application is *launched*. Keep this in mind
-if you plan to deploy your application by means of an installer on
-Microsoft Windows Vista or newer, because the Program Files folder is not
-writable. Choose a location that is writable to the application, such
-as the user's AppData folder, or a shared folder in case of multiple users
-needing to access the same data.
+Let's first take a look at the :file:`main.py` in our project directory.
+It contains a `my_settings` object which is appended to the global `settings.
+The :ref:`settings object<settings>` contains the global configuration for things such as database and file location.
Now we can look at :file:`model.py`. Camelot has already imported some classes
for us. They are used to create our entities. Let's say we want a movie entity
View
40 readme.txt
@@ -2,22 +2,34 @@
Camelot
##########
-A python GUI framework on top of Sqlalchemy and PyQt, inspired by the Django admin interface.
-Start building desktop applications at warp speed, simply by adding some additional information to your
-model definition::
+Camelot provides components for building business applications on top
+*SQLAlchemy* and *Qt*. It is inspired by the Django admin interface. A simple
+piece of code as this::
- class Movie(Entity):
- title = Field(Unicode(60), required=True)
- short_description = Field(Unicode(512))
- release_date = Field(Date)
- genre = Field(Unicode(15))
-
- class Admin(EntityAdmin):
- verbose_name = 'Movie'
- list_display = ['title', 'short_description', 'release_date', 'genre']
+ class Movie( Entity ):
+ title = Column( Unicode( 60 ), nullable = False )
+ short_description = Column( Unicode( 512 ) )
+ release_date = Column( Date() )
+
+ class Admin(EntityAdmin):
+ list_display = ['title', 'short_description', 'release_date']
-This piece of code is enough to define your database schema and to create a user friendly
-desktop GUI.
+Is enough to define your database schema, define the mapping between the
+database and objects, and to create a user friendly desktop GUI.
+
+Building applications with Camelot has these advantages :
+
+ * Use high quality editors together with the *Qt* Model-View framework
+
+ * Editors are bound to the model without writing binding code
+
+ * User friendliness and performance out of the box
+
+ * Tons of built in functions such as data import and export, printing,
+ backup and restore
+
+ * Documentation on creating the various parts of an application like wizards
+ and reports
For more information, refer to :
View
3  setup.py
@@ -9,7 +9,7 @@
setup(
name = 'Camelot',
version = camelot.__version__,
- description = 'A python GUI framework on top of Sqlalchemy, Elixir and PyQt, inspired by the Django admin interface. Start building desktop applications at warp speed, simply by adding some additional information to you model definition.',
+ description = 'A python GUI framework on top of Sqlalchemy and Qt, inspired by the Django admin interface. Start building desktop applications at warp speed, simply by adding some additional information to you model definition.',
long_description = long_description,
keywords = 'qt pyqt sqlalchemy elixir desktop gui framework',
author = 'Conceptive Engineering',
@@ -69,4 +69,3 @@
'Topic :: Software Development :: Libraries :: Application Frameworks',
],
packages = find_packages() + ['doc',] )
-
Please sign in to comment.
Something went wrong with that request. Please try again.