Source code for pyforms_web.widgets.django.modeladmin
from pyforms_web.basewidget import BaseWidget, segment
from pyforms_web.controls.control_button import ControlButton
from pyforms_web.controls.control_querylist import ControlQueryList
from pyforms_web.controls.control_emptywidget import ControlEmptyWidget
from pyforms_web.web.middleware import PyFormsMiddleware
from .modelform import ModelFormWidget
from django.db import models
[docs]class ModelAdminWidget(BaseWidget):
"""
The class is used to generate an admin interface for ModelAdmin.MODEL.
.. code:: python
from suppliers.models import Suplier
class SupplierAdminApp(ModelAdminWidget):
MODEL = Supplier
TITLE = 'Suppliers'
"""
MODEL = None #: class: Model to manage
TITLE = None #: str: Title of the application
EDITFORM_CLASS = ModelFormWidget #: class: Edit form class
ADDFORM_CLASS = None #: class: Create form class
USE_DETAILS_TO_ADD = True #: boolean: Use the flag to create the ControlEmptyWidget self._details. This control is used to load the ADDFORM_CLASS.
USE_DETAILS_TO_EDIT = True #: boolean: Use the flag to create the ControlEmptyWidget self._details. This control is used to load the EDITFORM_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
LIST_HEADERS = None #: list(str): Table columns headers. It will override the LIST_DISPLAY
LIST_COLS_SIZES = None #: list(str): Table columns sizes. Should use style units.
LIST_COLS_ALIGN = None #: list(str): Table columns alignments. Should use style units.
SEARCH_FIELDS = None #: list(str): Fields to be used in the search
EXPORT_CSV = False #: boolean: Flag to activate the export of data to csv. The value of this flag is overwritten by the function has_export_csv_permissions
EXPORT_CSV_COLUMNS = None #: list(str): List of fields to export to the csv file. By default it will assume the fields in the LIST_DISPLAY variable
EXPORT_CSV_HEADERS = {} #: dict(str: str): Provide custom header labels to fields listed in EXPORT_CSV_COLUMNS, e.g. {'date': 'Procedure Date'}
CONTROL_LIST = ControlQueryList #: class: Control to be used in to list the values
FIELDSETS = None #: Formset of the edit form
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') if 'title' in kwargs else self.TITLE
self.model = kwargs.get('model') if 'model' in kwargs else self.MODEL
self.editmodel_class = kwargs.get('editform_class') if 'editform_class' in kwargs else 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)
BaseWidget.__init__(self, title, *args, **kwargs)
user = PyFormsMiddleware.user()
#######################################################
self._list = self.CONTROL_LIST(
'List',
headers = self.LIST_HEADERS if self.LIST_HEADERS else None,
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,
export_csv = self.has_export_csv_permissions(user),
export_csv_columns = self.get_export_csv_columns(user),
export_csv_headers = self.EXPORT_CSV_HEADERS,
columns_size=self.LIST_COLS_SIZES if self.LIST_COLS_SIZES else None,
columns_align=self.LIST_COLS_ALIGN if self.LIST_COLS_ALIGN else None,
)
has_details = self.USE_DETAILS_TO_ADD or self.USE_DETAILS_TO_EDIT
if has_details:
self._details = ControlEmptyWidget('Details', visible=False)
##############################################
# Check if the add button should be included
has_add_permission = self.has_add_permissions() and self.addmodel_class is not None
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'
),
]
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()
#################################################################################
#### 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 ##################################################################
#################################################################################
[docs] def get_toolbar_buttons(self, has_add_permission=False):
"""
This function generate the formset configuration for the top buttons,
Returns:
list(str): Returns the formset configuration that will be append to
the end of the fieldsets.
"""
return '_add_btn' if has_add_permission else None
[docs] def populate_list(self):
"""
Function called to configure the CONTROL_LIST to display the data
"""
self._list.value = self.__get_queryset()
# force the list to be updated
self._list.mark_to_update_client()
[docs] 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
[docs] def hide_form(self):
"""
Function called to hide the form
"""
# hide details
if hasattr(self, '_details'): self._details.hide()
# show the buttons, only if the they exists:
toolbar = [self.toolbar] if isinstance(self.toolbar, str) else self.toolbar
if toolbar:
for o in toolbar:
if o and hasattr(self, o):
getattr(self, o).show()
self._list.show()
self._list.selected_row_id = -1
self.populate_list()
[docs] 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_permissions(): return
params = {
'title':'Create',
'model':self.model,
'parent_model':self.parent_model,
'parent_pk':self.parent_pk,
'parent_win': self
}
if self.INLINES: params.update({'inlines':self.INLINES})
if self.FIELDSETS: params.update({'fieldsets':self.FIELDSETS})
if self.READ_ONLY: params.update({'readonly':self.READ_ONLY})
createform = self.addmodel_class(**params)
if hasattr(self, '_details') and self.USE_DETAILS_TO_ADD:
self._list.hide()
self._details.show()
self._details.value = createform
toolbar = [self.toolbar] if isinstance(self.toolbar, str) else self.toolbar
if toolbar:
for o in toolbar:
if o and hasattr(self, o):
getattr(self, o).hide()
else:
self._list.show()
if hasattr(self, '_details'):
self._details.hide()
[docs] def show_edit_form(self, obj_pk=None):
"""
Show the edition for for a specific object
:param int obj_pk: Primary key of the object to be show in the edit form.
"""
obj = self.model.objects.get(pk=obj_pk)
# if there is no edit permission then does not show the form
if not self.has_view_permissions(obj): return
# 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
params = {
'title':'Edit',
'model':self.model,
'pk':obj.pk,
'parent_model':self.parent_model,
'parent_pk':self.parent_pk,
'parent_win': self
}
if self.INLINES: params.update({'inlines': self.INLINES} )
if self.FIELDSETS: params.update({'fieldsets':self.FIELDSETS})
if self.READ_ONLY: params.update({'readonly': self.READ_ONLY})
editmodel_class = self.get_editmodel_class(obj)
editform = editmodel_class(**params)
if hasattr(self, '_details') and self.USE_DETAILS_TO_EDIT:
self._details.value = editform
self._list.hide()
self._details.show()
# only if the button exists:
toolbar = [self.toolbar] if isinstance(self.toolbar, str) else self.toolbar
if toolbar:
for o in toolbar:
if o and hasattr(self, o): getattr(self, o).hide()
else:
self._list.show()
if hasattr(self, '_details'):
self._details.hide()
[docs] def get_editmodel_class(self, obj):
"""
Gets the pyforms app to edit the object
:param django.db.models.Model obj: Object to be edited
"""
return self.editmodel_class
[docs] 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 issubclass(parent_model, field.related_model):
self.parent_field = field
break
[docs] def has_add_permissions(self):
"""
Function called to check if one has permission to add new objects.
Returns:
bool: True if has add permission, False otherwise.
"""
queryset = self.model.objects.all()
if hasattr(queryset, 'has_add_permissions'):
return queryset.has_add_permissions( PyFormsMiddleware.user() )
else:
return True
[docs] def has_view_permissions(self, obj):
"""
Function called to check if one has permission to view the current queryset.
:param django.db.models.Model obj: object to view.
Returns:
bool: True if has view permissions, False otherwise.
"""
queryset = self.model.objects.filter(pk=obj.pk)
if hasattr(queryset, 'has_view_permissions'):
return queryset.has_view_permissions( PyFormsMiddleware.user() )
else:
return True
[docs] def has_remove_permissions(self, obj):
"""
Function called to check if one has permission to remove the current queryset.
:param django.db.models.Model obj: object to remove.
Returns:
bool: True if has remove permissions, False otherwise.
"""
return True
[docs] def has_update_permissions(self, obj):
"""
Function called to check if one has permission to update the current queryset.
:param django.db.models.Model obj: object to update.
Returns:
bool: True if has update permissions, False otherwise.
"""
return True
[docs] def has_export_csv_permissions(self, user):
"""
Function called to check if one has permission to export the objects to csv.
:param django.contrib.auth.models.User: User to check the permission.
Returns:
bool: True if has permissions, False otherwise.
"""
return self.EXPORT_CSV
[docs] def get_export_csv_columns(self, user):
"""
Function called to get the columns for the csv export.
:param django.contrib.auth.models.User: User to check the permission.
Returns:
list(str): List of columns names.
"""
return self.EXPORT_CSV_COLUMNS if self.EXPORT_CSV_COLUMNS is not None else self.LIST_DISPLAY
#################################################################################
#### 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:
# if the user has edit permission then
if self.has_view_permissions(obj):
self.object_pk = obj.pk
self._list.selected_row_id = None
self.show_edit_form(obj.pk)
else:
raise Exception('You do not have permissions to visualize this record.')
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(queryset, 'list_permissions'):
queryset = queryset.list_permissions(request.user)
if hasattr(self.model, 'get_queryset'):
queryset = self.model.get_queryset(request, queryset)
return self.get_queryset(request, queryset)