# Admin-like editor/browser of your models using DIALOG / WHIPTAIL / XDIALOG / ... # # License: GPL 2 # # Usage: # # 1) install python-dialog package and dialog-like program (dialog, whiptail, xdialog) # # 2) put this file to your project's top-level directory # # 3) into your settings.py put something like this: # DIALOG_APPS = ( # 'yourproject.yourapp', # 'yourproject.otherapp', # ... # ) # # ...optionally you can specify a dialog program: # DIALOG_DIALOG = '/usr/bin/whiptail' (I prefer) # or DIALOG_DIALOG = '/usr/bin/dialog' # or DIALOG_DIALOG = '/usr/bin/Xdialog' # # ...and some other settings, see bellow... # # 4) start this program: # ./manage.py shell # >>> from yourproject import dialogadmin # >>> dialogadmin.run() # # ...or you can create django-admin custom command # (see http://docs.djangoproject.com/en/dev/howto/custom-management-commands/#howto-custom-management-commands ) # # # Bugreports welcome... # import dialog from django.db import models from django.conf import settings from django.core.exceptions import ObjectDoesNotExist # From http://www.python.org/doc/2.5.2/lib/built-in-funcs.html # Helps me to import module like 'myproject.myapp.models' by string def my_import ( name ): mod = __import__ ( name ) components = name . split ( '.' ) for comp in components [ 1 :]: mod = getattr ( mod , comp ) return mod # Lets try import default settings, only required is DIALOG_APPS (iterable) try : DIALOG = str ( settings . DIALOG_DIALOG ) # absolute path to executable or name of executable' except AttributeError : DIALOG = 'dialog' try : DIALOGRC = str ( settings . DIALOG_DIALOGRC ) except AttributeError : DIALOGRC = None try : HEIGHT = int ( settings . DIALOG_HEIGHT ) except AttributeError : HEIGHT = 0 try : WIDTH = int ( settings . DIALOG_WIDTH ) except AttributeError : WIDTH = 0 # Inititialize Dialog object from python-dialog package D = dialog . Dialog ( dialog = DIALOG , DIALOGRC = DIALOGRC ) try : D . setBackgroundTitle ( str ( settings . DIALOG_BACKGROUND_TITLE )) except AttributeError : pass """ Initialize model control structure Example: Models['myproject.myapp']['model_name'] = modelclass """ Models = {} for app in settings . DIALOG_APPS : for Model in models . get_models ( my_import ( ' %s .models' % app )): try : Models [ app ][ Model . _meta . verbose_name ] = Model except KeyError : Models [ app ] = {} Models [ app ][ Model . _meta . verbose_name ] = Model ### These dialog_* methods shows DIALOG on the screen def dialog_menu ( msg , choices ): # choices are in the form (tag, item) return D . menu ( str ( msg ), width = WIDTH , height = HEIGHT , choices = tuple ( choices )) def dialog_radiolist ( msg , choices ): # choices are in the form (tag, item, status) and status is boolean return D . radiolist ( str ( msg ), width = WIDTH , height = HEIGHT , choices = tuple ( choices )) def dialog_msgbox ( msg ): return D . msgbox ( str ( msg ), width = WIDTH , height = HEIGHT ) # return (1, None) def dialog_yesno ( msg , default ): # 'default' is initial value of boolean type return int ( not D . yesno ( str ( msg ), width = WIDTH , height = HEIGHT , defaultno = not default )) def dialog_inputbox ( msg , default ): # default is initial string return D . inputbox ( str ( msg ), width = WIDTH , height = HEIGHT , init = str ( default )) def dialog_checklist ( msg , choices ): # choices are in the form (tag, item, status) and status is boolean return D . checklist ( str ( msg ), width = WIDTH , height = HEIGHT , choices = tuple ( choices )) def handler_AutoField (): # no args here return ( 1 , dialog_msgbox ( u'Cannot edit AutoField' )) # Called when edit ManyToManyField, arguments: obj=model instance, field=fieldclass def handler_ManyToManyField ( obj , field ): if obj . pk == None : return ( 1 , dialog_msgbox ( 'Object %s must be saved before editing ManyToManyField %s ' % ( obj , field . name ))) try : checked = getattr ( obj , field . name ) . all () except ValueError : checked = () choices = [ ( str ( o . pk ), str ( o ), int ( o in checked )) for o in field . rel . to . objects . all ()] ret = dialog_checklist ( 'Choose object(s) for %s : %s ' % ( obj , field . name ), choices ) objects = None if not ret [ 0 ]: objects = [ field . rel . to . objects . get ( pk = pk ) for pk in ret [ 1 ]] return ( ret [ 0 ], objects ) # Called when edit ForeignKey, arguments: obj=model instance, field=fieldclass def handler_ForeignKey ( obj , field ): try : default = getattr ( obj , field . name ) except ObjectDoesNotExist : default = None choices = [ ( str ( o . pk ), str ( o ), int ( o == default )) for o in field . rel . to . objects . all ()] ret = dialog_radiolist ( 'Choose object for %s : %s ' % ( obj , field . name ), choices ) obj = None if not ret [ 0 ]: obj = field . rel . to . objects . get ( pk = ret [ 1 ]) return ( ret [ 0 ], obj ) def handler_OneToOneField ( obj , field ): return handler_ForeignKey ( obj , field ) def handler_BooleanField ( msg , value ): return ( 0 , dialog_yesno ( msg , value )) # Called when no other handler is called def handler_BaseField ( msg , value ): return dialog_inputbox ( msg , value ) # Main field handler. It determines, which other field-handler to call, arguments: obj=model instance, field=fieldclass, field_value=value as string def field_handler ( obj , field , field_value ): if isinstance ( field , models . AutoField ): ret = handler_AutoField () elif isinstance ( field , models . ManyToManyField ): ret = handler_ManyToManyField ( obj , field ) elif isinstance ( field , models . ForeignKey ): ret = handler_ForeignKey ( obj , field ) elif isinstance ( field , models . OneToOneField ): ret = handler_OneToOneField ( obj , field ) elif isinstance ( field , models . BooleanField ): ret = handler_BooleanField ( 'Choose yes/no for field %s of object %s ' % ( field . name , obj ), field_value ) else : ret = handler_BaseField ( 'Edit field %s of object %s ' % ( field . name , obj ), field_value ) return ret # Mother of other AdminDialogs :)) class AdminDialog ( object ): pass # Shows dialog with list of apps class AppsAdminDialog ( AdminDialog ): def __init__ ( self ): choices = [( key , key ) for key in Models . keys ()] while True : ret = dialog_menu ( 'Choose app' , choices ) if ret [ 0 ]: break ModelsAdminDialog ( ret [ 1 ]) # Shows dialog with list of app's models class ModelsAdminDialog ( AdminDialog ): def __init__ ( self , app ): choices = [( key , key ) for key in Models [ app ] . keys ()] while True : ret = dialog_menu ( 'Choose model from %s ' % app , choices ) if ret [ 0 ]: break ActionAdminDialog ( Models [ app ][ ret [ 1 ]]) # Show dialog for choosing action class ActionAdminDialog ( AdminDialog ): def __init__ ( self , Model ): choices = [( key , key ) for key in ( 'browse/edit' , 'add' , 'delete' )] while True : ret = dialog_menu ( 'Choose action for %s ' % Model . _meta . verbose_name , choices ) if ret [ 0 ]: break action = ret [ 1 ] if action == 'browse/edit' : ObjectsAdminDialog ( Model , action , ObjectDetailAdminDialog ) elif action == 'delete' : ObjectsAdminDialog ( Model , action , ObjectDeleteAdminDialog ) elif action == 'add' : ObjectDetailAdminDialog ( Model ()) # Show dialog with list of all objects (model instances), Model=modelclass, action=string, next_dialog=class of AdminDiaalog to call next (detail or delete) class ObjectsAdminDialog ( AdminDialog ): def __init__ ( self , Model , action , next_dialog ): while True : choices = [( str ( o . pk ), str ( o )) for o in Model . objects . all ()] ret = dialog_menu ( 'Choose object to %s ' % action , choices ) if ret [ 0 ]: break next_dialog ( Model . objects . get ( pk = ret [ 1 ])) # Show dialog to ask for deletion, arguments: obj=model instance class ObjectDeleteAdminDialog ( AdminDialog ): def __init__ ( self , obj ): if dialog_yesno ( 'Delete object %s ?' % obj , False ): obj . delete () dialog_msgbox ( ' %s deleted' % obj ) # class ObjectDetailAdminDialog ( AdminDialog ): """ Creates choices to display on the screen Example: ( ( field1, field_value1 ), ( field2, field_value2 ), ... ) """ def _init_choices ( self , obj ): self . choices = [] for field_name in obj . _meta . get_all_field_names (): field = obj . _meta . get_field_by_name ( field_name )[ 0 ] if isinstance ( field , models . related . RelatedObject ): # Ignore this strange type continue elif isinstance ( field , models . ManyToManyField ): try : field_value = [ str ( M ) for M in getattr ( obj , field . name ) . all ()] # Create unicode list of ManyToMany related objects except ValueError : field_value = None else : try : field_value = getattr ( obj , field . name ) # Read other type of field except : field_value = None self . choices . append ( ( field . name , str ( field_value )) ) def __init__ ( self , obj ): changed = False # if object changes, this is set to True and finally yesno dialog asks for save while True : self . _init_choices ( obj ) ret = dialog_menu ( 'Choose field of %s ' % obj , self . choices ) if ret [ 0 ]: if changed : if dialog_yesno ( 'Save changes to %s ?' % obj , False ): try : obj . save () dialog_msgbox ( 'Object %s saved' % obj ) except Exception , e : dialog_msgbox ( 'An error occured saving object %s : %s ' % ( obj , e )) continue break field = obj . _meta . get_field_by_name ( ret [ 1 ])[ 0 ] # get fieldclass try : field_value = getattr ( obj , field . name ) # read value of field except : field_value = None ret = field_handler ( obj , field , field_value ) print ret [ 1 ] == field_value if not ret [ 0 ]: if ret [ 1 ] == field_value : # No change, let's continue continue setattr ( obj , field . name , ret [ 1 ]) changed = True # Set changed flag def run (): AppsAdminDialog ()