def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._people = ControlButton('<i class="icon users"></i>People', css=' circular ', label_visible=False, default='window.location="/app/people/";') self._contracts = ControlButton( '<i class="icon file outline"></i>Contracts', css=' circular ', label_visible=False, default='window.location="/app/contracts/";') self._proposals = ControlButton( '<i class="icon file"></i>Proposals', css=' circular ', label_visible=False, default='window.location="/app/proposals/";') self._orders = ControlButton('<i class="icon dollar"></i>Orders', css=' circular ', label_visible=False, default='window.location="/app/orders/";') self.formset = [ 'h2:Human resources', no_columns('_people', '_contracts', '_proposals'), '-', 'h2:Orders', no_columns('_orders') ]
def __init__(self, *args, **kwargs): super(DefaultApp, self).__init__(*args, **kwargs) self._css_btn = ControlButton( '<i class="icon toggle on" ></i>Toggle css', default=self.__toggle_css_evt, label_visible=False) self._toggle_btn = ControlButton( '<i class="icon eye" ></i>Toggle visibility', default=self.__toggle_visibility_evt, label_visible=False) self._copy_btn = ControlButton( '<i class="icon copy outline" ></i>Copy the text', default=self.__copy_text_evt, label_visible=False) self._input = ControlText( 'Type something here and press the copy button', changed_event=self.__input_changed_evt) self._text = ControlTextArea('Result') self._combo = ControlCombo('Combo', items=[('Item 1', 1), ('Item 2', 2), ('Item 3', 3)]) self._check = ControlCheckBox('Check box') self._list = ControlList('List') self._label = ControlLabel('Label', default='Use the label for a dynamic text') self.formset = [ no_columns('_toggle_btn', '_copy_btn', '_css_btn'), ' ', '_input', '_text', { 'Free text': [ 'h1:Header 1', 'h2:Header 2', 'h3:Header 3', 'h4:Header 4', 'h5:Header 5', 'h1-right:Header 1', 'h2-right:Header 2', 'h3-right:Header 3', 'h4-right:Header 4', 'h5-right:Header 5', '-', 'Free text here', 'msg:Message text', 'info:Info message', 'warning:Warning message', 'alert:Alert message' ], 'Segments': [ 'The next example has a segment', segment('_combo', '_check', css='secondary'), '_list', '_label' ] } ]
def __init__(self, *args, **kwargs): """ :param str title: Title of the app. By default will assume the value in the class variable TITLE. :param django.db.models.Model model: Model the App will manages. By default will assume the value in the class variable MODEL. :param class editform_class: Class used to generate the edition form. By default will assume the value in the class variable EDITFORM_CLASS. :param int parent_pk: (optional) Used to generate the inline interface. Primary key of the parent model :param Model parent_model: (optional) Used to generate the inline interface. Parent model """ title = kwargs.get('title', self.TITLE) self.model = kwargs.get('model', self.MODEL) self.editmodel_class = kwargs.get('editform_class', self.EDITFORM_CLASS) self.addmodel_class = kwargs.get('addform_class', self.ADDFORM_CLASS if self.ADDFORM_CLASS else self.editmodel_class) # Set the class to behave as inline ModelAdmin ######## self.parent_field = None self.parent_pk = kwargs.get('parent_pk', None) self.parent_model = kwargs.get('parent_model', None) if self.parent_model and self.parent_pk: self.set_parent(self.parent_model, self.parent_pk) has_add_permission = self.has_add_permission() and self.addmodel_class is not None has_edit_permission = self.has_edit_permission() and self.editmodel_class is not None BaseWidget.__init__(self, title) ####################################################### self._list = self.CONTROL_LIST( 'List', list_display = self.LIST_DISPLAY if self.LIST_DISPLAY else [], list_filter = self.LIST_FILTER if self.LIST_FILTER else [], search_fields= self.SEARCH_FIELDS if self.SEARCH_FIELDS else [], rows_per_page= self.LIST_ROWS_PER_PAGE, n_pages = self.LIST_N_PAGES ) has_details = (self.USE_DETAILS_TO_ADD or self.USE_DETAILS_TO_EDIT) and (has_add_permission or has_edit_permission) if has_details: self._details = ControlEmptyWidget('Details', visible=False) ############################################## # Check if the add button should be included if has_add_permission: self._add_btn = ControlButton( self.ADD_BTN_LABEL, label_visible=False, default=self.show_create_form ) if self.parent_model: self._add_btn.css = 'tiny basic blue' ############################################## self.toolbar = self.get_toolbar_buttons(has_add_permission=has_add_permission) if self.parent_model: self.formset = [ self.toolbar, '_details' if has_details else None, '_list', ] else: self.formset = [ '_details' if has_details else None, segment( self.toolbar, '_list' ), ] # if the user has edit permission then if has_edit_permission: # events self._list.item_selection_changed_event = self.__list_item_selection_changed_event #if it is a inline app, add the title to the header if self.parent_model and self.title: self.formset = ['h3:'+str(title)]+self.formset self.populate_list()
def __init__(self, *args, **kwargs): """ :param str title: Title of the app. By default will assume the value in the class variable TITLE. :param django.db.models.Model model: Model with the App will represent. By default will assume the value in the class variable MODEL. :param list(ModelAdmin) inlines: Sub models to show in the interface :param list(str) fieldsets: Organization of the fields :param int parent_pk: Parent model key :param django.db.models.Model parent_model: Parent model class :param int pk: Model register to manage """ BaseWidget.__init__(self, *args, **kwargs) self.model = kwargs.get('model', self.MODEL) self.inlines = kwargs.get('inlines', self.INLINES) self.fieldsets = kwargs.get('fieldsets', self.FIELDSETS) self.readonly = kwargs.get('readonly', self.READ_ONLY) self.has_cancel_btn = kwargs.get('has_cancel_btn', self.HAS_CANCEL_BTN) if self.fieldsets is None: self.fieldsets = self.FIELDSETS self._auto_fields = [] self._callable_fields = [] self.edit_fields = [] self.edit_buttons = [] self.inlines_apps = [] self.inlines_controls_name = [] self.inlines_controls = [] self.object_pk = None # used to configure the interface to inline # it will filter the dataset by the foreign key self.parent_field = None self.parent_pk = kwargs.get('parent_pk', None) self.parent_model = kwargs.get('parent_model', None) if self.parent_model and self.parent_pk: self.__set_parent(self.parent_model, self.parent_pk) ####################################################### # buttons self._save_btn = ControlButton(self.SAVE_BTN_LABEL) self._create_btn = ControlButton(self.CREATE_BTN_LABEL) self._remove_btn = ControlButton(self.REMOVE_BTN_LABEL, css='red basic') if self.has_cancel_btn: self._cancel_btn = ControlButton(self.CANCEL_BTN_LABEL, css='gray basic') if self.parent_model: self._save_btn.css += ' tiny' self._create_btn.css += ' tiny' self._remove_btn.css += ' tiny' if self.has_cancel_btn: self._cancel_btn.css += ' tiny' self.edit_buttons.append(self._save_btn) self.edit_buttons.append(self._create_btn) self.edit_buttons.append(self._remove_btn) if self.has_cancel_btn: self.edit_buttons.append(self._cancel_btn) self.edit_fields += self.edit_buttons for field in self.edit_fields: field.hide() # events self._create_btn.value = self.__create_btn_event self._remove_btn.value = self.__remove_btn_event self._save_btn.value = self.__save_btn_event if self.has_cancel_btn: self._cancel_btn.value = self.cancel_btn_event self._create_btn.label_visible = False self._remove_btn.label_visible = False self._save_btn.label_visible = False if self.has_cancel_btn: self._cancel_btn.label_visible = False self.create_model_formfields() pk = kwargs.get('pk', None) if pk: self.object_pk = pk self.show_edit_form() else: self.show_create_form() for inline in self.inlines: self.formset.append(inline.__name__)
class EditFormAdmin(BaseWidget): MODEL = None #: class: Model to manage TITLE = None #: str: Title of the application INLINES = [] #: list(class): Sub models to show in the interface FIELDSETS = None #: Formset of the edit form READ_ONLY = [] #: list(str): List of readonly fields HAS_CANCEL_BTN = True #: bool: Flag to show or hide the cancel button #: str: Label for the save button SAVE_BTN_LABEL = '<i class="save icon"></i> Save' #: str: Label for the create button CREATE_BTN_LABEL = '<i class="plus icon"></i> Create' #: str: Label for the cancel button CANCEL_BTN_LABEL = '<i class="hide icon"></i> Close' #: str: Label for the delete button REMOVE_BTN_LABEL = '<i class="trash outline icon"></i> Remove' #: str: Label for the popup window for the delete confirmation POPUP_REMOVE_TITLE = 'The next objects are going to be affected or removed' def __init__(self, *args, **kwargs): """ :param str title: Title of the app. By default will assume the value in the class variable TITLE. :param django.db.models.Model model: Model with the App will represent. By default will assume the value in the class variable MODEL. :param list(ModelAdmin) inlines: Sub models to show in the interface :param list(str) fieldsets: Organization of the fields :param int parent_pk: Parent model key :param django.db.models.Model parent_model: Parent model class :param int pk: Model register to manage """ BaseWidget.__init__(self, *args, **kwargs) self.model = kwargs.get('model', self.MODEL) self.inlines = kwargs.get('inlines', self.INLINES) self.fieldsets = kwargs.get('fieldsets', self.FIELDSETS) self.readonly = kwargs.get('readonly', self.READ_ONLY) self.has_cancel_btn = kwargs.get('has_cancel_btn', self.HAS_CANCEL_BTN) if self.fieldsets is None: self.fieldsets = self.FIELDSETS self._auto_fields = [] self._callable_fields = [] self.edit_fields = [] self.edit_buttons = [] self.inlines_apps = [] self.inlines_controls_name = [] self.inlines_controls = [] self.object_pk = None # used to configure the interface to inline # it will filter the dataset by the foreign key self.parent_field = None self.parent_pk = kwargs.get('parent_pk', None) self.parent_model = kwargs.get('parent_model', None) if self.parent_model and self.parent_pk: self.__set_parent(self.parent_model, self.parent_pk) ####################################################### # buttons self._save_btn = ControlButton(self.SAVE_BTN_LABEL) self._create_btn = ControlButton(self.CREATE_BTN_LABEL) self._remove_btn = ControlButton(self.REMOVE_BTN_LABEL, css='red basic') if self.has_cancel_btn: self._cancel_btn = ControlButton(self.CANCEL_BTN_LABEL, css='gray basic') if self.parent_model: self._save_btn.css += ' tiny' self._create_btn.css += ' tiny' self._remove_btn.css += ' tiny' if self.has_cancel_btn: self._cancel_btn.css += ' tiny' self.edit_buttons.append(self._save_btn) self.edit_buttons.append(self._create_btn) self.edit_buttons.append(self._remove_btn) if self.has_cancel_btn: self.edit_buttons.append(self._cancel_btn) self.edit_fields += self.edit_buttons for field in self.edit_fields: field.hide() # events self._create_btn.value = self.__create_btn_event self._remove_btn.value = self.__remove_btn_event self._save_btn.value = self.__save_btn_event if self.has_cancel_btn: self._cancel_btn.value = self.cancel_btn_event self._create_btn.label_visible = False self._remove_btn.label_visible = False self._save_btn.label_visible = False if self.has_cancel_btn: self._cancel_btn.label_visible = False self.create_model_formfields() pk = kwargs.get('pk', None) if pk: self.object_pk = pk self.show_edit_form() else: self.show_create_form() for inline in self.inlines: self.formset.append(inline.__name__) ################################################################################# #### PROPERTIES ################################################################# ################################################################################# @property def model_object(self): """ django.db.models.Model object: Return the current object in edition. """ if self.object_pk is None: return None else: queryset = self.model.objects.all() # check if the model has a query_set function # if so use it to get the data for visualization if hasattr(self.model, 'get_queryset'): request = PyFormsMiddleware.get_request() queryset = self.model.get_queryset(request, queryset) return queryset.get(pk=self.object_pk) ################################################################################# #### FUNCTIONS ################################################################## ################################################################################# def get_buttons_row(self): """ This function generate the formset configuration for the save, create, cancel and remove buttons, Returns: list(str): Returns the formset configuration that will be append to the end of the fieldsets. """ buttons = ['_save_btn', '_create_btn'] if self.has_cancel_btn: buttons = buttons + ['_cancel_btn', ' '] buttons = buttons + ['_remove_btn'] return [no_columns(*buttons)] def hide_form(self): """ This functions hides the create and edit form. """ if self.parent and hasattr(self.parent, 'hide_form'): self.parent.hide_form() else: for field in self.edit_fields: field.hide() for field in self.inlines_controls: field.hide() def show_form(self): """ This shows the create and edit form. """ for field in self.edit_fields: field.show() for field in self.inlines_controls: field.show() def cancel_btn_event(self): """ Event called when the cancel button is pressed """ self.hide_form() def autocomplete_search(self, keyword, field): """ Function used by a combobox to get the items dynamically :param str keyword: Keyword for filter the results :param django.db.models.fields.Field field: Django field where the autocomplete will be applied Returns: list(dict): Results for the search in the format .. code-block:: python [{'name':name, 'value':id, 'text':text}, ...] """ query = field.related_model.objects.all() query = self.related_field_queryset(field, query) if keyword: if hasattr(field.related_model, 'autocomplete_search_fields'): or_filter = Q() for search_field in field.related_model.autocomplete_search_fields( ): or_filter.add(Q(**{search_field: keyword}), Q.OR) else: or_filter = Q(pk=keyword) else: or_filter = Q() try: return [{ 'name': str(o), 'value': o.pk, 'text': str(o) } for o in query.filter(or_filter)] except: return [] def related_field_queryset(self, field, queryset): """ Function called to manages the query for related fields like ForeignKeys and ManyToMany. :param django.db.models.fields.Field field: Related django field. :param django.db.models.query.QuerySet queryset: Default queryset for the related field. Returns: django.db.models.query.QuerySet: Results for the search in the format. """ return queryset def update_related_field(self, field, pyforms_field, queryset): """ Function called update the related fields like ForeignKeys and ManyToMany. :param django.db.models.fields.Field field: Related django field. :param ControlBase pyforms_field: Pyforms field that will be updated. :param django.db.models.query.QuerySet queryset: Default queryset for the related field. """ pass """ if isinstance(field, models.ForeignKey): pass #Foreign key #pyforms_field.clear_items() #if field.null: # pyforms_field.add_item( '', '-1' ) #for instance in query: # pyforms_field.add_item( str(instance), instance.pk ) elif isinstance(field, models.ManyToManyField): #Many to Many field #pyforms_field.queryset = query pass """ def show_create_form(self): """ This function prepares the fields to be shown as create form. """ #check if it has permissions to add new registers if ( self.parent and hasattr(self.parent, 'has_add_permission') ) and \ not self.parent.has_add_permission(): raise Exception('Your user does not have permissions to add') fields2show = self.get_visible_fields_names() self.__update_related_fields() # clear all the fields for field_name in fields2show: if hasattr(self, field_name): pyforms_field = getattr(self, field_name) pyforms_field.value = None for field in self.edit_fields: field.show() for inline in self.inlines_controls: inline.hide() self._save_btn.hide() self._remove_btn.hide() def update_callable_fields(self): if not self._callable_fields: return obj = self.model_object if obj is None: return for field_name in self._callable_fields: pyforms_field = getattr(self, field_name) value = getattr(obj, field_name)() pyforms_field.value = value def update_autonumber_fields(self): if not self._auto_fields: return obj = self.model_object if obj is None: return for field_name in self._auto_fields: pyforms_field = getattr(self, field_name) value = getattr(obj, field_name) pyforms_field.value = value def show_edit_form(self, pk=None): """ This function prepares the fields to be shown as edit form. :param int pk: Primiry key of the object to be show in the edit form. Returns: :django.db.models.Model object: Returns the object in edition. """ if pk: self.object_pk = pk for field in self.edit_fields: field.show() for field in self.inlines_controls: field.show() self._create_btn.hide() self.__update_related_fields() obj = self.model_object fields2show = self.get_visible_fields_names() for field_name in fields2show: if hasattr(self, field_name) and hasattr(obj, field_name): pyforms_field = getattr(self, field_name) value = getattr(obj, field_name) try: field = self.model._meta.get_field(field_name) except FieldDoesNotExist: try: field = getattr(self.model, field_name) except AttributeError: continue if callable(field) and not isinstance(field, models.Model): pyforms_field.value = value() elif field_name in self.readonly: if isinstance(field, models.ManyToManyField): pyforms_field.value = ';'.join( [str(o) for o in value.all()]) elif isinstance(value, datetime.datetime): if not value: pyforms_field.value = '' else: value = timezone.localtime(value) pyforms_field.value = value.strftime( '%Y-%m-%d %H:%M') elif isinstance(value, datetime.date): if not value: pyforms_field.value = '' else: pyforms_field.value = value.strftime('%Y-%m-%d') else: pyforms_field.value = value elif isinstance(field, models.AutoField): pyforms_field.value = value elif isinstance(field, models.FileField): pyforms_field.value = value.url if value else None elif isinstance(field, models.ImageField): pyforms_field.value = value.url if value else None elif isinstance(field, models.ForeignKey): pyforms_field.value = value.pk if value else None elif isinstance(field, models.ManyToManyField): pyforms_field.value = [str(o.pk) for o in value.all()] else: pyforms_field.value = value self.inlines_apps = [] for inline in self.inlines: pyforms_field = getattr(self, inline.__name__) pyforms_field._name = inline.__name__ app = inline(parent_model=self.model, parent_pk=self.object_pk) self.inlines_apps.append(app) pyforms_field.value = app pyforms_field.show() return obj def delete_event(self): """ Function called to delete the current object in edition. Returns: :bool: True if the object was deleted with success, False if not. """ if self.object_pk: obj = self.model_object obj.delete() self.object_pk = None self._remove_btn.hide() self._create_btn.show() self._save_btn.hide() for field in self.inlines_controls: field.hide() return True else: return False def popup_remove_handler(self, popup=None, button=None): """ Function that handles the buttons events of the object delete confirmation popup. :param BaseWidget popup: Popup application. :param str button: Label of the pressed button. """ if button == self.CANCEL_BTN_LABEL: popup.close() elif button == self.REMOVE_BTN_LABEL: if self.delete_event(): self.success('The object was deleted with success!', 'Success!') popup.close() else: popup.warning('The object was not deleted!', 'Warning!') def create_newobject(self): """ Function called to create a new object of the model. Returns: :django.db.models.Model object: Created object """ return self.model() def save_event(self): """ Function called when the save is called. Returns: :django.db.models.Model object: Created object or None if the object was not saved with success. """ fields2show = self.get_visible_fields_names() try: obj = self.model_object ## create an object if does not exists #### if obj is None: #check if it has permissions to add new registers if ( self.parent and hasattr(self.parent, 'has_add_permission') ) and \ not self.parent.has_add_permission(): raise Exception( 'Your user does not have permissions to add') obj = self.create_newobject() ########################################### # if it is working as an inline edition form # if self.parent_field: setattr(obj, self.parent_field.name, self.parent_model.objects.get(pk=self.parent_pk)) ############################################## for field in self.model._meta.get_fields(): if field.name not in fields2show: continue if field.name in self.readonly: continue pyforms_field = getattr(self, field.name) value = pyforms_field.value if isinstance(field, models.AutoField): continue elif isinstance(field, models.FileField): getattr(self, field.name).error = False value = getattr(self, field.name).value if value: try: os.makedirs( os.path.join(settings.MEDIA_ROOT, field.upload_to)) except os.error as e: pass paths = [p for p in value.split('/') if len(p) > 0][1:] from_path = os.path.join(settings.MEDIA_ROOT, *paths) if os.path.exists(from_path): to_path = os.path.join(settings.MEDIA_ROOT, field.upload_to, os.path.basename(value)) os.rename(from_path, to_path) url = '/'.join([field.upload_to] + [os.path.basename(value)]) if url[0] == '/': url = url[1:] setattr(obj, field.name, url) elif field.null: setattr(obj, field.name, None) else: setattr(obj, field.name, '') elif isinstance(field, models.ForeignKey): if value is not None: try: value = field.related_model.objects.get(pk=value) except: self.alert('The field [{0}] has an error.'.format( field.verbose_name)) pyforms_field.error = True else: value = None setattr(obj, field.name, value) elif not isinstance(field, models.ManyToManyField): pyforms_field.error = False setattr(obj, field.name, value) try: obj.full_clean() except ValidationError as e: html = '<ul class="list">' for field_name, messages in e.message_dict.items(): try: getattr(self, field_name).error = True label = get_lookup_verbose_name(self.model, field_name) html += '<li><b>{0}</b>'.format(label.capitalize()) field_error = True except FieldDoesNotExist: field_error = False except AttributeError: field_error = False if field_error: html += '<ul>' for msg in messages: html += '<li>{0}</li>'.format(msg) if field_error: html += '</ul></li>' html += '</ul>' self.alert(html) return None obj.save() for field in self.model._meta.get_fields(): if isinstance(field, models.ManyToManyField) and hasattr( self, field.name): values = getattr(self, field.name).value field_instance = getattr(obj, field.name) objs = field.related_model.objects.filter(pk__in=values) values_2_remove = field_instance.all().exclude( pk__in=[o.pk for o in objs]) for o in values_2_remove: field_instance.remove(o) values_2_add = objs.exclude( pk__in=[o.pk for o in field_instance.all()]) for o in values_2_add: field_instance.add(o) self.object_pk = obj.pk self.update_callable_fields() self.update_autonumber_fields() return obj except Exception as e: traceback.print_exc() self.alert(str(e)) return None ################################################################################# #### PRIVATE FUNCTIONS ########################################################## ################################################################################# def __set_parent(self, parent_model, parent_pk): """ Set the form to work as inline :param django.db.models.Model parent_model: Parent model. :param int parent_pk: Parent object primary key. """ self.parent_pk = parent_pk self.parent_model = parent_model for field in self.model._meta.get_fields(): if isinstance(field, models.ForeignKey): if parent_model == field.related_model: self.parent_field = field break def get_visible_fields_names(self): """ Function called to get names of the visible fields. Returns: :list(str): List names of the visible fields. """ if self.fieldsets: fields = get_fieldsets_strings(self.fieldsets) else: fields = [] for field in self.model._meta.get_fields(): if field.one_to_many: continue if field.one_to_one and field.name.endswith('_ptr'): continue fields.append(field.name) if self.parent_field: try: fields.remove(self.parent_field.name) except ValueError: pass return [field for field in fields if field is not None] def __update_related_fields(self): """ Update all related fields """ fields2show = self.get_visible_fields_names() formset = [] for field in self.model._meta.get_fields(): if not isinstance(field, (models.ForeignKey, models.ManyToManyField)): continue if field.name not in fields2show: continue #only update this field if is visible if field.name in self.readonly: continue pyforms_field = getattr(self, field.name) queryset = self.related_field_queryset( field, field.related_model.objects.all()) self.update_related_field(field, pyforms_field, queryset) def create_model_formfields(self): """ Create the model edition form. """ fields2show = self.get_visible_fields_names() formset = [] for field_name in fields2show: # if the field already exists then ignore the creation if hasattr(self, field_name): continue try: field = self.model._meta.get_field(field_name) except FieldDoesNotExist: try: field = getattr(self.model, field_name) except AttributeError: continue pyforms_field = None if not (callable(field) and not isinstance(field, models.Model)): label = get_lookup_verbose_name(self.model, field_name) if callable(field) and not isinstance(field, models.Model): label = getattr(field, 'short_description') if hasattr( field, 'short_description') else field_name pyforms_field = ControlText(label.capitalize(), readonly=True) self._callable_fields.append(field_name) elif field.name in self.readonly: if isinstance(field, models.TextField): pyforms_field = ControlTextArea(label.capitalize(), readonly=True) else: pyforms_field = ControlText(label.capitalize(), readonly=True) elif isinstance(field, models.AutoField): pyforms_field = ControlText(label.capitalize(), readonly=True) self._auto_fields.append(field_name) elif isinstance(field, models.Field) and field.choices: pyforms_field = ControlCombo(label.capitalize(), items=[(c[1], c[0]) for c in field.choices]) elif isinstance(field, models.BigIntegerField): pyforms_field = ControlInteger(label.capitalize()) elif isinstance(field, models.BooleanField): pyforms_field = ControlCheckBox(label.capitalize()) elif isinstance(field, models.DateTimeField): pyforms_field = ControlDateTime(label.capitalize()) elif isinstance(field, models.DateField): pyforms_field = ControlDate(label.capitalize()) elif isinstance(field, models.DecimalField): pyforms_field = ControlFloat(label.capitalize()) elif isinstance(field, models.FileField): pyforms_field = ControlFileUpload(label.capitalize()) elif isinstance(field, models.FloatField): pyforms_field = ControlFloat(label.capitalize()) elif isinstance(field, models.ImageField): pyforms_field = ControlFileUpload(label.capitalize()) elif isinstance(field, models.IntegerField): pyforms_field = ControlInteger(label.capitalize()) elif isinstance(field, models.TextField): pyforms_field = ControlTextArea(label.capitalize()) elif isinstance(field, models.NullBooleanField): pyforms_field = ControlCombo(label.capitalize(), items=[('Unknown', None), ('Yes', True), ('No', False)]) elif isinstance(field, models.ForeignKey): url = "/pyforms/autocomplete/{app_id}/{field_name}/{{query}}/".format( app_id=self.uid, field_name=field.name) pyforms_field = ControlAutoComplete(label.capitalize(), items_url=url, model=field.related_model) elif isinstance(field, models.ManyToManyField): url = "/pyforms/autocomplete/{app_id}/{field_name}/{{query}}/".format( app_id=self.uid, field_name=field.name) pyforms_field = ControlAutoComplete(label.capitalize(), items_url=url, model=field.related_model, multiple=True) else: pyforms_field = ControlText(label.capitalize()) # add the field to the application if pyforms_field is not None: setattr(self, field_name, pyforms_field) formset.append(field_name) self.edit_fields.append(pyforms_field) #Create the inlines edition forms. self.inlines_controls_name = [] self.inlines_controls = [] for inline in self.inlines: pyforms_field = ControlEmptyWidget() pyforms_field.name = inline.__name__ pyforms_field._parent = self setattr(self, inline.__name__, pyforms_field) self.inlines_controls_name.append(inline.__name__) self.inlines_controls.append(pyforms_field) self.formset = self.fieldsets if self.fieldsets else formset self.formset = self.formset + self.get_buttons_row() def __create_btn_event(self): """ Event called by the create button """ self.object_pk = None obj = self.save_event() if obj: self._create_btn.hide() self._save_btn.show() self._remove_btn.show() for i, field in enumerate(self.inlines_controls): app = self.inlines_apps[i] app.populate_list() app.parent_pk = obj.pk field.show() self.success( 'The object <b>{0}</b> was saved with success!'.format(obj), 'Success!') def __save_btn_event(self): """ Event called by the save button """ obj = self.save_event() if obj: self.success( 'The object <b>{0}</b> was saved with success!'.format(obj), 'Success!') def __remove_btn_event(self): """ Event called by the remove button """ def related_objects(obj): objects = [] for rel in list(obj.__class__._meta.related_objects): f = {rel.field.name: obj} rel_objects = rel.related_model.objects.filter(**f) for o in rel_objects: objects.append((o, related_objects(o))) return objects def related_objects_html(objects): html = "<ul>" for o, objs in objects: html += "<li>" html += "{1}: <b>{0}</b>".format( str(o), o.__class__._meta.verbose_name.title()) if len(objs) > 0: html += related_objects_html(objs) html += "</li>" html += "</ul>" return html if self.object_pk: obj = self.model_object objects = obj, related_objects(obj) html = related_objects_html([objects]) popup = self.warning_popup( html, self.POPUP_REMOVE_TITLE, buttons=[self.REMOVE_BTN_LABEL, self.CANCEL_BTN_LABEL], handler=self.popup_remove_handler) popup.button_0.css = 'basic red'
class ModelAdmin(BaseWidget): """ Class used to generate automatically an admin interface for a specific model """ MODEL = None #: class: Model to manage TITLE = None #: str: Title of the application EDITFORM_CLASS = EditFormAdmin #: class: Edit form class INLINES = [] #: list(class): Sub models to show in the interface LIST_FILTER = None #: list(str): List of filters fields LIST_DISPLAY = None #: list(str): List of fields to display in the table SEARCH_FIELDS = None #: list(str): Fields to be used in the search FIELDSETS = None #: Formset of the edit form CONTROL_LIST = ControlQueryList #: class: Control to be used in to list the values READ_ONLY = [] #: list(str): List of readonly fields LIST_ROWS_PER_PAGE = 10 #: int: number of rows to show per page LIST_N_PAGES = 5 #: int: number of pages to show in the list bottom #: str: Label of the add button ADD_BTN_LABEL = '<i class="plus icon"></i> Add' def __init__(self, *args, **kwargs): """ :param str title: Title of the app. By default will assume the value in the class variable TITLE. :param django.db.models.Model model: Model the App will manages. By default will assume the value in the class variable MODEL. :param class editform_class: Class used to generate the edition form. By default will assume the value in the class variable EDITFORM_CLASS. :param int parent_pk: (optional) Used to generate the inline interface. Primary key of the parent model :param Model parent_model: (optional) Used to generate the inline interface. Parent model """ title = kwargs.get('title', self.TITLE) self.model = kwargs.get('model', self.MODEL) self.editmodel_class = kwargs.get('editform_class', self.EDITFORM_CLASS) # Set the class to behave as inline ModelAdmin ######## self.parent_field = None self.parent_pk = kwargs.get('parent_pk', None) self.parent_model = kwargs.get('parent_model', None) if self.parent_model and self.parent_pk: self.set_parent(self.parent_model, self.parent_pk) has_add_permission = self.has_add_permission() has_edit_permission = self.has_edit_permission() BaseWidget.__init__(self, title) ####################################################### self._list = self.CONTROL_LIST( 'List', list_display = self.LIST_DISPLAY if self.LIST_DISPLAY else [], list_filter = self.LIST_FILTER if self.LIST_FILTER else [], search_fields= self.SEARCH_FIELDS if self.SEARCH_FIELDS else [], rows_per_page= self.LIST_ROWS_PER_PAGE, n_pages = self.LIST_N_PAGES ) if has_add_permission or has_edit_permission: self._details = ControlEmptyWidget('Details', visible=False) ############################################## # Check if the add button should be included if has_add_permission: self._add_btn = ControlButton( self.ADD_BTN_LABEL, label_visible=False, default=self.show_create_form ) if self.parent_model: self._add_btn.css = 'tiny basic blue' ############################################## if self.parent_model: self.formset = [ '_add_btn' if has_add_permission else None, '_list', '_details' if has_add_permission or has_edit_permission else None, ] else: self.formset = [ segment( '_add_btn' if has_add_permission else None, '_list' ), '_details' if has_add_permission or has_edit_permission else None, ] # if the user has edit permission then if has_add_permission: # events self._list.item_selection_changed_event = self.__list_item_selection_changed_event #if it is a inline app, add the title to the header if self.parent_model: self.formset = ['h3:'+str(title)]+self.formset self.populate_list() ################################################################################# #### PROPERTIES ################################################################# ################################################################################# @property def selected_row_object(self): """ django.db.models.Model: Return the current selected row object. If no row is selected return None. """ if int(self._list.selected_row_id)<0: return None return self._list.value.get(pk=self._list.selected_row_id) ################################################################################# #### FUNCTIONS ################################################################## ################################################################################# def populate_list(self): """ Function called to configure the CONTROL_LIST to display the data """ self._list.value = self.__get_queryset() def get_queryset(self, request, queryset): """ The function retrives the queryset used to polulate the list. :param django.db.models.query.QuerySet queryset: Default queryset used to populate the list. This queryset may have already applied the next filters: - If this class is being used as a inline app, the filters to select only the rows related with the parent app are applied. - If the model being managed by this class has a function called get_queryset(request, queryset), the filters applied by this function are applied. (this helps maintaining the visualization rules on the side of the model) Returns: django.db.models.query.QuerySet: Returns the queryset used to populate the list. """ return queryset def hide_form(self): """ Function called to hide the form """ # only if the button exists: # if there is not add permission the add button is not created. if hasattr(self, '_add_btn'): self._add_btn.show() self._list.show() self._list.selected_row_id = -1 self.populate_list() self._details.hide() def show_create_form(self): """ Show an empty for for creation """ # if there is no add permission then does not show the form if not self.has_add_permission(): return self._add_btn.hide() self._list.hide() self._details.show() createform = self.editmodel_class( title='Create', model=self.model, inlines=self.INLINES, parent_model=self.parent_model, parent_pk=self.parent_pk, fieldsets=self.FIELDSETS, readonly=self.READ_ONLY, parent_win=self ) self._details.value = createform def show_edit_form(self, pk=None): """ Show the edition for for a specific object :param int pk: Primary key of the object to edit """ # if there is no edit permission then does not show the form if not self.has_edit_permission(): return # only if the button exists: # if there is not add permission the add button is not created. if hasattr(self, '_add_btn'): self._add_btn.hide() self._list.hide() self._details.show() # create the edit form a add it to the empty widget details # override the function hide_form to make sure the list is shown after the user close the edition form editform = self.editmodel_class( title='Edit', model=self.model, pk=pk, inlines=self.INLINES, parent_model=self.parent_model, parent_pk=self.parent_pk, fieldsets=self.FIELDSETS, readonly=self.READ_ONLY, parent_listapp=self ) self._details.value = editform def set_parent(self, parent_model, parent_pk): """ Function called to set prepare the Application to work as an inline :param django.db.models.Model parent_model: Model of the parent Edition form :param int parent_pk: Primary key of the parent object """ self.parent_pk = parent_pk self.parent_model = parent_model for field in self.model._meta.get_fields(): if isinstance(field, models.ForeignKey): if parent_model == field.related_model: self.parent_field = field break def has_add_permission(self): """ Function called to check if one has permission to add new objects. Returns: bool: True if has add permission, False otherwise. """ return True def has_edit_permission(self): """ Function called to check if one has permission to edit the objects. Returns: bool: True if has edit permission, False otherwise. """ return True ################################################################################# #### PRIVATE FUNCTIONS ########################################################## ################################################################################# def __list_item_selection_changed_event(self): """ Event called when a row is selected. It shows the edition for row. """ obj = self.selected_row_object if obj: self.object_pk = obj.pk self.show_edit_form(obj.pk) def __get_queryset(self): """ """ queryset = self.model.objects.all() #used to filter the model for inline fields if self.parent_field: queryset = queryset.filter(**{self.parent_field.name: self.parent_pk}) # check if the model has a query_set function # if so use it to get the data for visualization request = PyFormsMiddleware.get_request() if hasattr(self.model, 'get_queryset'): queryset = self.model.get_queryset(request, queryset) return self.get_queryset(request, queryset)
class DefaultApp(BaseWidget): TITLE = 'Demo app' LAYOUT_POSITION = 0 def __init__(self, *args, **kwargs): super(DefaultApp, self).__init__(*args, **kwargs) self._css_btn = ControlButton( '<i class="icon toggle on" ></i>Toggle css', default=self.__toggle_css_evt, label_visible=False) self._toggle_btn = ControlButton( '<i class="icon eye" ></i>Toggle visibility', default=self.__toggle_visibility_evt, label_visible=False) self._copy_btn = ControlButton( '<i class="icon copy outline" ></i>Copy the text', default=self.__copy_text_evt, label_visible=False) self._input = ControlText( 'Type something here and press the copy button', changed_event=self.__input_changed_evt) self._text = ControlTextArea('Result') self._combo = ControlCombo('Combo', items=[('Item 1', 1), ('Item 2', 2), ('Item 3', 3)]) self._check = ControlCheckBox('Check box') self._list = ControlList('List') self._label = ControlLabel('Label', default='Use the label for a dynamic text') self.formset = [ no_columns('_toggle_btn', '_copy_btn', '_css_btn'), ' ', '_input', '_text', { 'Free text': [ 'h1:Header 1', 'h2:Header 2', 'h3:Header 3', 'h4:Header 4', 'h5:Header 5', 'h1-right:Header 1', 'h2-right:Header 2', 'h3-right:Header 3', 'h4-right:Header 4', 'h5-right:Header 5', '-', 'Free text here', 'msg:Message text', 'info:Info message', 'warning:Warning message', 'alert:Alert message' ], 'Segments': [ 'The next example has a segment', segment('_combo', '_check', css='secondary'), '_list', '_label' ] } ] def __toggle_css_evt(self): if self._css_btn.css == 'basic green': self._css_btn.css = 'inverted red' self._label.css = 'red' self._css_btn.label = '<i class="icon toggle on" ></i>Toggle css' else: self._css_btn.css = 'basic green' self._label.css = 'inverted green' self._css_btn.label = '<i class="icon toggle off" ></i>Toggle css' def __toggle_visibility_evt(self): if self._input.visible: self._input.hide() else: self._input.show() if self._text.visible: self._text.hide() else: self._text.show() if self._copy_btn.visible: self._copy_btn.hide() else: self._copy_btn.show() if self._css_btn.visible: self._css_btn.hide() else: self._css_btn.show() def __copy_text_evt(self): self._text.value = self._input.value def __input_changed_evt(self): print(self._input.value)