# 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()