Camelot: Desktop Applications at Warp Speed

Camelot

Desktop Applications at Warp Speed

erik.janssens@conceptive.be

jeroen@shore.be

www.python-camelot.com

  • Python
  • Qt
  • sqlalchemy

Press the right arrow, down arrow, or spacebar to advance; press the left arrow or up arrow to move backward.
You can also click the left and right arrows in the control bar at the top. The control bar appears when you hover over it

Slideshow code stolen from ravelrumba.com
Syntax highlighting used: softwaremaniacs.org
Fonts:ChunkFive and Latin Modern Mono

The Quest of Lancelot the Brave

Getting started

easy_install camelot
camelot_admin.py startproject videostore

WTF is Camelot

First shalt thou create the Elixir model

	
vi videostore/model.py

class Movie(Entity):
	title = Field(Unicode(60), required=True)
	short_description = Field(Unicode(512))
	release_date = Field(Date)

	class Admin(EntityAdmin):
		verbose_name = _('Movie')
		list_display = [
			'title', 
			'short_description', 
			'release_date'
		]
	

http://elixir.ematia.de/trac/wiki/TutorialDivingIn

The result - list view

The result - form view

Mixing Python and SQL

	
class Movie(Entity):
	title = Field(Unicode(60), required=True)
	short_description = Field(Unicode(512))
	release_date = Field(Date)

	@ColumnProperty
	def total_visitors(self):
		return sql.select(
			[sql.func.sum(VisitorReport.visitors)],
			VisitorReport.movie_id==self.id
		)

	class Admin(EntityAdmin):
		verbose_name = _('Movie')
		list_display = ['title', 'total_visitors'] 
	

ColumnProperty

The view definition

			
class Admin( Party.Admin ):
	list_display = ['first_name', 'last_name', 'email', 'phone']
	form_display = TabForm( [
		(_('Basic'),['first_name','last_name', 'picture',]),
		(_('Official'), ['birthdate','passport_number']),
		(_('Work'), ['employers',]
] )
			
		

Convention and Configuration

List of strings → List of objects → Subclassing

			
form_display = ['first_name', 'last_name']
==
form_display = Form(['first_name', 'last_name'])



list_filter = ['status']
==
list_filter = [GroupBoxFilter('status')]				
			
		

Relations

			
class Movie(Entity):
	...
	director = ManyToOne('Director')

class Director(Entity):
	using_options(tablename='director')

	name = Field(Unicode(60))
	movies = OneToMany('Movie')

	class Admin(EntityAdmin):
		verbose_name = _('Director')
		list_display = ['name', 'movies']				
			
		

Relations demonstrated

About 30 minutes later ...

New record demonstrated

Other sources: Open format - Closed format - YouTube

Search demonstrated

Other sources: Open format - Closed format - YouTube

Filters

			
class Movie(Entity):
	...
	class Admin(EntityAdmin):
		...
		list_filter = ['director.name']
		...			
			
		

Filters demonstrated

Other sources: Open format - Closed format - YouTube

Specific filters

			
class Movie(Entity):
	...
	class Admin(EntityAdmin):
		...
		from camelot.view.filters import ComboBoxFilter
		list_filter = [ComboBoxFilter('director.name')]
		...
			
		

Specific filters demonstrated

Other sources: Open format - Closed format - YouTube

Export to Excel

Other sources: Open format - Closed format - YouTube

How the table view stays responsive

How the table view stays responsive

Other sources: Open format - Closed format - YouTube

How would the customer react ?

How would the customer react ?

How would the customer react ?

Field attributes

Dynamic background colors

			
def release_date_background_color(o):
	import datetime
  	from PyQt4 import QtGui
  	if o.release_date < datetime.date(1999,7,1):
		return QtGui.QColor('#800080')
	else:
		return None

class Admin(EntityAdmin):
	...          
	field_attributes = {	
		'release_date':{
			'background_color':release_date_background_color}
		}
			
		

Dynamic background colors demonstrated

Other sources: Open format - Closed format - YouTube

Dynamic Tooltips

			
def show_movies(o):
	return ', '.join(
		[movie.title for movie in o.director.movies]
	)

	class Admin(EntityAdmin):
		...  
		field_attributes = {
			'director': {'tooltip':show_movies}
		}
			
		

Dynamic Tooltip demonstrated

Empower the user

Translations demonstrated

Other sources: Open format - Closed format - YouTube

Translations QT Linguist

Keep it coming

Actions, reports

			
class Movie(Entity):
	...
	class Admin(EntityAdmin):
		from movie_summary import MovieSummary
		...
		form_actions = [MovieSummary(_('Summary'))]
		...
			
		

Actions, reports – cont'd

			
from camelot.admin.form_action import PrintHtmlFormAction

class MovieSummary(PrintHtmlFormAction):

	def html(self, o):
		import datetime
		import os
		from jinja import Environment, FileSystemLoader

		...
			
		

Actions, reports – cont'd

			
...

e = Environment(loader=FileSystemLoader('templates'))
context = {
	'header':o.title,
	'title':_('Movie Summary'),
	'style':'.label { font-weight:bold; }',
	'content':'<span class="label">Description:</span> %s<br>\
		<span class="label">Release date:</span> %s<br>\
		<span class="label">Genre:</span> %s<br>\
		<span class="label">Director:</span> %s'
		% (o.short_description,o.release_date,o.genre, o.director),
	'cover':os.path.join('media','covers',o.cover_image.name ),
	'footer':'<br>copyright %s - Camelot' %
		datetime.datetime.now().year
}
t = e.get_template('movie_summary.html')
return t.render(context)
			
		

Report Action demonstrated

Other sources: Open format - Closed format - YouTube

Meeting the customer

Delegate tasks to QT

Use custom delegates

			
class Movie(Entity):
	...
	class Admin(EntityAdmin):
		from quarter_delegate import QuarterDelegate
		...
		field_attributes = {
			'on_dvd':{'delegate':QuarterDelegate}
		}
		...
			
		

Delegates demonstrated

Reading minds

Tabs in forms

			
class Movie(Entity):
	...
	class Admin(EntityAdmin):
		form_display = TabForm([
			(_('Basic'), 
			TabForm([
				(_('Movie'), ['title','short_description']),
				(_('Other'), ['genre', 'director']),
			], position=TabForm.WEST)),
			(_('Cover image'), ['cover_image']),
			(_('Dates'), ['release_date', 'on_dvd'])
		])
			
		

Tab Form demonstrated

Other sources: Open format - Closed format - YouTube

Deploying the Bootstrapper

Can't sleep at night?

Unittests

			
#The model → unittest library does the job
#Nose for test discovery
#The view → ??
# QT has a nice feature :
pixmap = QPixmap.grabWidget(widget)
# Just subclass Camelot's EntityViewsTest :
from camelot.test import  EntityViewsTest
class MyEntityViewsTest(EntityViewsTest):
    pass
			
		

Unittests demonstrated

Other sources: Open format - Closed format - YouTube

Camelot

Desktop Applications at Warp Speed

erik.janssens@conceptive.be

jeroen@shore.be

www.python-camelot.com

project-camelot@googlegroups.com

  • Python
  • Qt
  • sqlalchemy

Thank you for your attention

Questions ?


reload