Source code for pyforms_web.basewidget
try:
from .controls.control_player import ControlPlayer
except:
pass
from .controls.control_base import ControlBase
from .controls.control_label import ControlLabel
from .controls.control_button import ControlButton
from .web.middleware import PyFormsMiddleware
from pyforms_web.organizers import no_columns, segment
from django.template.loader import render_to_string
from .utils import make_lambda_func
from confapp import conf
import uuid, os, inspect, dill, simplejson, filelock
from django.core.exceptions import PermissionDenied
[docs]class BaseWidget(object):
"""
The class implements a application form
"""
# TODO: Implement this
URLS = None #: list(str): Django urls to be added to the urls.py file
TITLE = None #: str: Title of the application.
#: int or str: Id of the layout handler function registered in the javascript by the function [pyforms.register_layout_place] or Element DOM id in the HTML where the application should be shown.
LAYOUT_POSITION = None
#: str: Time in milliseconds to refresh the application.
REFRESH_TIMEOUT = None
#: list(str): List of django groups authorized to run the application
AUTHORIZED_GROUPS = None
#: str: Css classes to add to the form.
CSS = ''
#: str: Static files to include in the page
STATIC_FILES = []
def __init__(self, *args, **kwargs):
"""
:param str title: Title of the app. By default will assume the value in the class variable TITLE.
:param BaseWidget parent_win: Parent BaseWidget
**Example:**
.. code:: python
class FeedViewerApp(BaseWidget):
TITLE = 'Feed viewer'
def __init__(self, *args, **kwargs):
self._likebtn = ControlButton(label_visible=False, labeled=True)
self._htmlviewer = ControlTemplate('Html', template=self.VIEWER_TEMPLATE)
self.formset = ['_likebtn', '_htmlviewer']
"""
self._formset = None
self._splitters = []
self._title = kwargs.get('title', args[0] if len(args)>0 else self.TITLE)
self._formLoaded = False
self._controls = []
self._html = ''
self._js = ''
self._css = kwargs.get('css', self.CSS)
self.refresh_timeout= kwargs.get('refresh_timeout', self.REFRESH_TIMEOUT)
self._close_widget = False
self.init_form_result = None
self._uid = self.UID if hasattr(self, 'UID') and self.UID else 'a'+str(uuid.uuid4())
self._messages = []
self._js_code2execute = [];
self.parent = kwargs.get('parent_win', None)
self.is_new_app = True
if not self.has_session_permissions(PyFormsMiddleware.user()):
raise PermissionDenied('The user does not have access to the app [{0}]'.format(self.title))
PyFormsMiddleware.add(self)
############################################################################
############ FUNCTIONS #####################################################
############################################################################
[docs] def init_form(self, parent=None):
"""
Generate the application Form. Return the dict:
.. code:: python
{
'code': ..., # HTML code that will initialize the application.
'title': ..., # Title of the application.
'css': ..., # Application CSS.
'app_id': ..., # Application id.
'refresh_timeout': ... # Application refresh time.
}
"""
self._html = ''
self._js = ''
self._controls = [c.init_form() for c in self.controls.values()]
if self._formset != None:
self._html += self.generate_panel(self._formset)
parent_code = 'undefined'
if parent: parent_code = "'{0}'".format(parent.uid)
extra_data = {'refresh_timeout': self.refresh_timeout, 'messages':self._messages}
modulename = inspect.getmodule(self).__name__ + '.' + self.__class__.__name__
self._js = '[{0}]'.format(",".join(self._controls))
self._html += """
<script type="text/javascript">pyforms.add_app( new BaseWidget('{2}', '{0}', {1}, {3}, {4}) );{extra_code}</script>
""".format(modulename, self._js, self.uid, parent_code, simplejson.dumps(extra_data), extra_code=';'.join(self._js_code2execute))
self._formLoaded = True
self._messages = []
self.mark_to_update_client()
res = {
'code': self._html,
'title': self._title,
'css': self._css,
'app_id':self.uid,
'refresh_timeout': self.refresh_timeout
}
return res
def generate_segment(self, row):
"""
Generate the html to organize the formset in segments
"""
html = "<div class='field {field_css}' style='{field_style}' ><div class='ui segment pyforms-segment {css}' style='{style}' >".format(
css=row.css,
style=row.style,
field_css=row.field_css,
field_style=row.field_style)
html += self.generate_panel( list(row) )
html += "</div></div>"
return html
[docs] def generate_nocolumns(self, formset):
"""
Generate the html for the no_columns organizer
"""
layout = "<div class='row fields {size} {css}' style='{style}' >".format(
size=self.__get_fields_class(formset),
css=formset.css,
style=formset.style
)
for row in formset:
layout += self.generate_panel( row )
return layout+"</div>"
def generate_tabs(self, formsetdict):
"""
Generate QTabWidget for the module form
@param formset: Tab form configuration
@type formset: dict
"""
tabs_head = ""
tabs_body = ""
tab_id = uuid.uuid4()
for index, (key, item) in enumerate( sorted(formsetdict.items()) ):
if item is None: continue
active = 'active' if index==0 else ''
tabs_body += "<div class='ui bottom attached {3} tab segment' data-tab='{4}-{5}' id='{0}-tab{1}' >{2}</div>".format(tab_id, index, self.generate_panel(item), active, tab_id, index)
tabs_head += "<div class='{1} item' data-tab='{2}-{3}' >{0}</div>".format(key[key.find(':')+1:], active, tab_id, index)
return """<div id='{0}' class='ui top attached tabular menu' >{1}</div>{2}<script type='text/javascript'>$('#{0}.menu .item').tab();</script>""".format(tab_id, tabs_head, tabs_body)
def generate_control(self, row):
control = self.controls.get(row, None)
if control==None:
if row==' ': return "<div class='field' ></div>"
if row.startswith('empty:'): return "<div class='field {0} wide' ></div>".format(row[6:])
elif row.startswith('h1:'): return "<h1 class='field' >{0}</h1>".format(row[3:])
elif row.startswith('h1-right:'):return "<h1 class='ui right aligned header field' >{0}</h1>".format(row[9:])
elif row.startswith('h2:'): return "<h2 class='field' >{0}</h2>".format(row[3:])
elif row.startswith('h2-right:'):return "<h2 class='ui right aligned header field' >{0}</h2>".format(row[9:])
elif row.startswith('h3:'): return "<h3 class='field' >{0}</h3>".format(row[3:])
elif row.startswith('h3-right:'):return "<h3 class='ui right aligned header field' >{0}</h3>".format(row[9:])
elif row.startswith('h4:'): return "<h4 class='field' >{0}</h4>".format(row[3:])
elif row.startswith('h4-right:'):return "<h4 class='ui right aligned header field' >{0}</h4>".format(row[9:])
elif row.startswith('h5:'): return "<h5 class='field' >{0}</h5>".format(row[3:])
elif row.startswith('h5-right:'):return "<h5 class='ui right aligned header field' >{0}</h5>".format(row[9:])
elif row.startswith('info:'): return "<div class='ui info visible message'>{0}</div>".format(row[5:])
elif row.startswith('warning:'): return "<div class='ui warning visible message'>{0}</div>".format(row[8:])
elif row.startswith('alert:'): return "<div class='ui error visible message'>{0}</div>".format(row[6:])
elif row.startswith('msg:'): return "<div class='ui message'>{0}</div>".format(row[4:])
elif row.startswith('text:'): return "<div class='field'>{0}</div>".format(row[5:])
elif row == '-': return "<div class='ui clearing divider'></div>"
else: return row
else:
return str(control)
[docs] def generate_panel(self, formset):
"""
Generate a panel for the application form with all the controls:
:param list formset: formset configuration, used to generate the panel.
**Example:**
.. code-block:: python
[
no_columns('_toggle_btn','_copy_btn', '_css_btn'),
' ',
('empty:twelve','_input'),
'_text',
{
'a: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'
],
'b:Segments': [
'The next example has a segment',
segment(
'_combo',
'_check',
css='secondary'
),
'_list',
'_label'
]
}
]
- **tuple**: displays the controls horizontally.
- **list**: displays the controls vertically.
- **dict**: displays the controls in Tabs.
- Use [a:,b:,c:] prefix to sort the tabs.
- **'-'**: Draw a vertical line.
- **' '**: Empty column.
- **Empty column**: Use ' ', or the prefix 'empty:' + size of the column (ex: one, two, ..., sixteen) to add a empty column.
- **segment**: Wraps the formset around a segment (Semantic UI segment).
- Call the parameter **css**, to add extra classes to the segment.
- **no_columns**: Do not apply the fields columns alignments.
- **Free text**: Do not apply the fields columns alignments.
- **Message**: By using the prefixes [msg:,info:,warning:,alert:] you will wrap a free message on message box.
- **Headers**: Use the prefixes [h1:,h2:,h3:,h4:,h5:,h1-right:,h2-right:,h3-right:,h4-right:,h5-right:] on free text.
"""
if formset is None:
return ''
elif 'notifications-area' == formset:
return "<div class='notifications-area field'></div>"
elif '=' in formset:
index = list( formset ).index('=')
return "<div id='{splitter_id}' class='horizontalSplitter' ><div>{top}</div><div>{bottom}</div></div>".format(
splitter_id = uuid.uuid4(),
top = self.generate_panel(formset[0:index]),
bottom = self.generate_panel(formset[index+1:])
)
elif '||' in formset:
index = list( formset ).index('||')
return "<div id='{splitter_id}' class='verticalSplitter' ><div>{left}</div><div>{right}</div></div>".format(
splitter_id = uuid.uuid4(),
left = self.generate_panel(formset[0:index]),
right = self.generate_panel(formset[index+1:])
)
elif isinstance(formset, tuple ):
layout = "<div class='row fields {0}' >".format(self.__get_fields_class(formset))
for row in formset:
layout += self.generate_panel( row )
return layout+"</div>"
elif isinstance(formset, no_columns ):
return self.generate_nocolumns(formset)
elif isinstance(formset, segment):
return self.generate_segment(formset)
elif isinstance(formset, list):
layout = ""
for row in formset:
if row == ' ':
layout += "<div class='field-empty-space' ></div>"
else:
layout += self.generate_panel( row )
return layout
elif isinstance(formset, dict ):
return self.generate_tabs(formset)
else:
return self.generate_control(formset)
def save_form(self, data={}, path=None):
"""
Called to save the form
TODO
"""
pass
def load_form(self, data, path=None):
"""
Called to load a form
TODO
"""
pass
def save_window(self):
pass
def load_window(self):
pass
def load_form_filename(self, filename):
"""
Load the forms from a file
TODO
"""
pass
[docs] def close(self):
"""
Close the application
"""
self.mark_to_update_client()
self._close_widget = True
[docs] def message(self, msg, title=None, msg_type=None):
"""
Write a simple message.
:param str msg: Message to show.
:param str title: Message title.
:param str msg_type: Message box css class.
"""
msg = { 'type': msg_type if msg_type else '', 'messages':msg if isinstance(msg, list) else [msg], 'title':title }
self._messages.append(msg)
self.mark_to_update_client()
[docs] def success(self, msg, title=None):
"""
Write a success message
:param str msg: Message to show.
:param str title: Message title.
"""
self.message(msg, title, msg_type='success')
[docs] def info(self, msg, title=None):
"""
Write a info message
:param str msg: Message to show.
:param str title: Message title.
"""
self.message(msg, title, msg_type='info')
[docs] def warning(self, msg, title=None):
"""
Write a warning message
:param str msg: Message to show.
:param str title: Message title.
"""
self.message(msg, title, msg_type='warning');
[docs] def alert(self, msg, title=None):
"""
Write a alert message
:param str msg: Message to show.
:param str title: Message title.
"""
self.message(msg, title, msg_type='error')
[docs] def message_popup(self, msg, title='', buttons=None, handler=None, msg_type=''):
"""
Show a popup message window
:param str msg: Message to show.
:param str title: Message title.
:param list(str) buttons: List of buttons labels to create in the popup window.
:param str msg_type: Message box css class.
:param method handler: Method that will handle the press of the buttons.
.. code:: python
# Handler
def button_pressed_btn(popup=[Popup instance], button=[Label of the pressed button]):
...
"""
self._active_popup_msg = PopupWindow(title, msg, buttons, handler, msg_type=msg_type, parent_win=self)
return self._active_popup_msg
[docs] def success_popup(self, msg, title='', buttons=None, handler=None):
"""
Show a popup success message window
:param str msg: Message to show.
:param str title: Message title.
:param list(str) buttons: List of buttons labels to create in the popup window.
:param method handler: Method that will handle the press of the buttons.
"""
return self.message_popup(msg, title, buttons, handler, msg_type='positive')
[docs] def info_popup(self, msg, title='', buttons=None, handler=None):
"""
Show a popup info message window
:param str msg: Message to show.
:param str title: Message title.
:param list(str) buttons: List of buttons labels to create in the popup window.
:param method handler: Method that will handle the press of the buttons.
"""
return self.message_popup(msg, title, buttons, handler, msg_type='info')
[docs] def warning_popup(self, msg, title='', buttons=None, handler=None):
"""
Show a popup warning message window
:param str msg: Message to show.
:param str title: Message title.
:param list(str) buttons: List of buttons labels to create in the popup window.
:param method handler: Method that will handle the press of the buttons.
"""
return self.message_popup(msg, title, buttons, handler, msg_type='warning')
[docs] def alert_popup(self, msg, title='', buttons=None, handler=None):
"""
Show a popup alert message window
:param str msg: Message to show.
:param str title: Message title.
:param list(str) buttons: List of buttons labels to create in the popup window.
:param method handler: Method that will handle the press of the buttons.
"""
return self.message_popup(msg, title, buttons, handler, msg_type='error')
##########################################################################
############ WEB functions ###############################################
##########################################################################
def __get_fields_class(self, row):
"""
Get the css class to be used on the controls organization
"""
if isinstance(row, no_columns): return 'no-alignment'
if len(row)==2: return 'two'
elif len(row)==3: return 'three'
elif len(row)==4: return 'four'
elif len(row)==5: return 'five'
elif len(row)==6: return 'six'
elif len(row)==7: return 'seven'
elif len(row)==8: return 'eight'
elif len(row)==9: return 'nine'
elif len(row)==10: return 'ten'
elif len(row)==11: return 'eleven'
elif len(row)==12: return 'twelve'
elif len(row)==13: return 'thirteen'
elif len(row)==14: return 'fourteen'
elif len(row)==15: return 'fiveteen'
elif len(row)==16: return 'sixteen'
elif len(row)==17: return 'seventeen'
elif len(row)==18: return 'eighteen'
elif len(row)==19: return 'nineteen'
elif len(row)==20: return 'twenty'
elif len(row)==21: return 'twentyone'
elif len(row)==22: return 'twentytwo'
else: return ''
[docs] def commit(self):
"""
Save all the application updates to a file, so it can be used in the next session.
"""
for key, item in self.controls.items(): item.commit()
user = PyFormsMiddleware.user()
# save the modifications
userpath = os.path.join(
conf.PYFORMS_WEB_APPS_CACHE_DIR,
'{0}-{1}'.format(user.pk, user.username)
)
if not os.path.exists(userpath): os.makedirs(userpath)
app_path = os.path.join(userpath, "{0}.app".format(self.uid) )
lock = filelock.FileLock(conf.PYFORMS_WEB_LOCKFILE)
with lock.acquire(timeout=4):
with open(app_path, 'wb') as f:
dill.dump(self, f, protocol=4)
[docs] def execute_js(self, code):
"""
This function executes a javascript remotely on the client side.
:param str code: Javascript code to execute.
"""
self._js_code2execute.append(code)
self.mark_to_update_client()
[docs] def mark_to_update_client(self):
"""
Used to flag pyforms that the application was updated and the updates should be sent to the client side
"""
request = PyFormsMiddleware.get_request()
if request is not None and \
hasattr(request,'updated_apps'):
request.updated_apps.add_top(self)
[docs] def deserialize_form(self, params):
"""
Load the json parameters sent by the client side
:param dict params: Data to load.
"""
widgets = []
#if hasattr(self, 'parent') and isinstance(self.parent, str):
# self.parent = PyFormsMiddleware.get_instance(self.parent)
for key, value in params.items():
control = self.controls.get(key, None)
if control!=None:
if control.__class__.__name__=='ControlEmptyWidget':
widgets.append( (control, params[key]) )
else:
control.deserialize(params[key])
for control, data in widgets: control.deserialize(data)
if 'event' in params.keys():
control = params['event']['control']
if control in self.controls.keys():
item = self.controls[control]
func = getattr(item, params['event']['event'])
func()
elif control=='self':
func = getattr(self, params['event']['event'])
func()
[docs] def serialize_form(self):
"""
Serialize the Form to a control.
Returns:
dict: Data representings the current state of the application.
"""
res = {
'uid': self.uid,
'layout_position': self.LAYOUT_POSITION if hasattr(self, 'LAYOUT_POSITION') else 5,
'title': self.title,
'close_widget': self._close_widget,
'js-code': list(self._js_code2execute),
'refresh_timeout': self.refresh_timeout
}
self._js_code2execute = []
if len(self._messages)>0:
res.update({'messages': self._messages})
if self._formLoaded: self._messages = []
for key, item in self.controls.items():
if item.was_updated:
res[item._name] = item.serialize()
try:
if isinstance(item, ControlPlayer ) and item._value!=None and item._value!='':
item._value.release() #release any open video
except:
pass
if self.parent:
self._parent_win_id = self.parent.uid
return res
[docs] @classmethod
def has_permissions(cls, user):
"""
This class method, verifies if a user has permissions to execute the application
:param User params: User to availuate the permissions.
"""
if hasattr(cls, 'AUTHORIZED_GROUPS') and cls.AUTHORIZED_GROUPS is not None:
if user.is_superuser and 'superuser' in cls.AUTHORIZED_GROUPS:
return True
if user.groups.filter(name__in=cls.AUTHORIZED_GROUPS).exists():
return True
else:
return True
return False
[docs] def has_session_permissions(self, user):
"""
It verifies if a user has permissions to execute the application during the runtime.
:param User params: User to availuate the permissions.
"""
return self.has_permissions(user)
##########################################################################
############ EVENTS ######################################################
##########################################################################
def before_close_event(self):
"""
Function called before the Form is closed.
TODO
"""
pass
##########################################################################
############ WEB events ##################################################
##########################################################################
[docs] def refresh_event(self):
"""
Event called every X time defined by REFRESH_TIMEOUT variable.
"""
pass
############################################################################
############ Properties ####################################################
############################################################################
@property
def controls(self):
"""
Returns all the form controls from the the module
"""
result = {}
for name, var in vars(self).items():
if isinstance(var, ControlBase):
var._name = name
var.parent = self
result[name]= var
return result
@property
def form(self):
"""
Return the basewidget html. The html is based on the 'basewidget-template.html' template
"""
return render_to_string(
os.path.join('pyforms', 'basewidget-template.html'),
{'application_html': self._html, 'application_id': self.uid}
)
@property
def title(self):
"""
Return and set the title of the application
"""
return self._title
@title.setter
def title(self, value): self._title = value
@property
def mainmenu(self):
"""
Return and set the mainmenu
TODO
"""
return None
@mainmenu.setter
def mainmenu(self, value):
pass
@property
def formset(self):
"""
Return and set the controls organization in the form
"""
return self._formset
@formset.setter
def formset(self, value): self._formset = value
@property
def uid(self):
"""
Return and set the application unique identifier
"""
return self._uid
@uid.setter
def uid(self, value): self._uid = value
@property
def visible(self):
"""
Return a boolean indicating if the form is visible or not
"""
return True
@property
def refresh_timeout(self):
"""
Return a boolean indicating if the form is visible or not
"""
return self._refresh_timeout
@refresh_timeout.setter
def refresh_timeout(self, value): self._refresh_timeout = value
@property
def parent(self):
if hasattr(self, '_parent_win_id'):
return PyFormsMiddleware.get_instance(self._parent_win_id)
else:
return self._parent_win
@parent.setter
def parent(self, value):
if hasattr(self, '_parent_win_id'):
del self._parent_win_id
self._parent_win = value
self.mark_to_update_client()
############################################################################
############ WEB Properties ################################################
############################################################################
@property
def js(self):
"""
Return the form javascript
"""
return self._js
class PopupWindow(BaseWidget):
LAYOUT_POSITION = conf.LAYOUT_NEW_WINDOW
def __init__(self, title, msg, buttons, handler, msg_type, parent_win=None):
BaseWidget.__init__(self, title, parent_win=parent_win)
self._label = ControlLabel(default=msg)
self._label.field_css = msg_type
buttons_formset = []
if buttons:
for i, b in enumerate(buttons):
name = 'button_{0}'.format(i)
setattr(self, name, ControlButton(b, label_visible=False))
getattr(self, name ).value = make_lambda_func(handler, popup=self, button=b)
buttons_formset.append(name)
self.formset = ['_label'] + ([no_columns(buttons_formset)] if buttons_formset else [])