Browse Source

more work on dataeditor. EditorLoadFieldset now dynamically builds a subform for dynamic dataset parameterization

master
phryk 4 weeks ago
parent
commit
b2aa94dcb6
5 changed files with 208 additions and 25 deletions
  1. 1
    1
      example.py
  2. 1
    0
      poobrains/__init__.py
  3. 3
    3
      poobrains/form/__init__.py
  4. 10
    0
      poobrains/form/types.py
  5. 193
    21
      poobrains/svg/plot.py

+ 1
- 1
example.py View File

@@ -184,7 +184,7 @@ class StockWeekly(poobrains.svg.plot.Dataset):
184 184
 
185 185
 class ConstrainedRandom(poobrains.svg.plot.Dataset):
186 186
 
187
-    def fill(self, magnitude=2, length=16):
187
+    def fill(self, magnitude: int = 2, length: int = 16) -> None:
188 188
         #app.debugger.set_trace()
189 189
         magnitude, length = int(magnitude), int(length)
190 190
 

+ 1
- 0
poobrains/__init__.py View File

@@ -131,6 +131,7 @@ class DummySession(werkzeug.datastructures.CallbackDict, flask.sessions.SessionM
131 131
     modified = False
132 132
     accessed = False
133 133
 
134
+
134 135
 new_session_funcs = set()
135 136
 def new_session(f):
136 137
 

+ 3
- 3
poobrains/form/__init__.py View File

@@ -119,8 +119,9 @@ class BaseForm(poobrains.rendering.Renderable, metaclass=FormMeta):
119 119
 
120 120
         elif isinstance(value, fields.BaseField) or isinstance(value, Fieldset):
121 121
             value.name = name
122
-            value.prefix = "%s.%s" % (self.prefix, self.name) if self.prefix else self.name
123
-            self.fields[name] = value
122
+            #value.prefix = "%s.%s" % (self.prefix, self.name) if self.prefix else self.name
123
+            #self.fields[name] = value
124
+            self.add_field(value)
124 125
 
125 126
         elif isinstance(value, Button):
126 127
             value.name = name
@@ -410,7 +411,6 @@ class Fieldset(BaseForm):
410 411
         self.errors = []
411 412
         super(Fieldset, self).__init__(*args, **kw)
412 413
 
413
-    
414 414
 
415 415
     def render(self, mode=None):
416 416
 

+ 10
- 0
poobrains/form/types.py View File

@@ -76,3 +76,13 @@ class DateTimeParamType(ParamType):
76 76
                         self.fail("'%s' is not a valid datetime. Expected format '%Y-%m-%d %H:%M:%S' or '%Y-%m-%d %H:%M:%S.%f'" % value)
77 77
 
78 78
 DATETIME = DateTimeParamType()
79
+
80
+
81
+lookup_table = {
82
+    int: IntParamType,
83
+    float: FloatParamType,
84
+    str: StringParamType,
85
+    bool: BoolParamType,
86
+    datetime.datetime: DateTimeParamType,
87
+    datetime.date: DateParamType
88
+}

+ 193
- 21
poobrains/svg/plot.py View File

@@ -3,9 +3,10 @@ import math
3 3
 import time
4 4
 import datetime
5 5
 import json
6
+import inspect
6 7
 import pandas
7 8
 
8
-from poobrains import Markup, app, request, abort, redirect, g, session, locked_cached_property, new_session
9
+from poobrains import Markup, app, request, abort, flash, redirect, g, session, locked_cached_property, new_session
9 10
 
10 11
 import poobrains.helpers
11 12
 import poobrains.errors
@@ -27,6 +28,13 @@ def dynamic_datasets():
27 28
     return datasets
28 29
 
29 30
 
31
+#def get_dataset_parameters(dataset_name):
32
+#    """ get parameters for dynamic datasets' `fill` function """
33
+
34
+
35
+
36
+
37
+
30 38
 def load_dataset(handle):
31 39
     if handle.startswith('_'): # underscore marks dynamic datasets in URLs
32 40
 
@@ -162,7 +170,7 @@ class StoredDataset(Dataset, poobrains.commenting.Commentable):
162 170
         if name == 'data':
163 171
             if self._data is None:
164 172
                 #self._data = pandas.DataFrame.from_dict(json.loads(self.data))
165
-                self._data = pandas.DataFrame.from_dict(super(StoredDataset, self).__getattribute__('data'))
173
+                self._data = pandas.DataFrame.from_dict(json.loads(super(StoredDataset, self).__getattribute__('data')))
166 174
             return self._data
167 175
         return super(StoredDataset, self).__getattribute__(name)
168 176
 
@@ -511,7 +519,7 @@ def new_editor_handle():
511 519
 
512 520
     handle = poobrains.helpers.random_string_light()
513 521
 
514
-    if 'editor-sessions' not in session or handle not in session['editor-sessions']:
522
+    if handle not in session['editor-sessions']:
515 523
         return handle
516 524
 
517 525
     return new_editor_handle()
@@ -536,51 +544,215 @@ def dataset_choices():
536 544
     choices = [(dynamic, 'Dynamic Datasets'), (stored, 'Stored Datasets')]
537 545
     return choices
538 546
 
547
+def dynamic_dataset_parameters(dataset_class):
548
+
549
+    r = {}
550
+    signature = inspect.signature(dataset_class.fill)
551
+    for param in signature.parameters.values():
552
+
553
+        if param.name == 'self':
554
+            continue # next loop iteration
555
+
556
+        if param.annotation == param.empty:
557
+            param_type = str
558
+
559
+        else:
560
+            param_type = param.annotation
561
+
562
+        r[param.name] = param_type
563
+
564
+    return r
565
+
566
+
567
+def validate_handle_not_in_session(handle):
568
+    if handle in session['editor-sessions']:
569
+        raise poobrains.errors.ValidationError("Editor Session named '%s' already exists!" % handle)
570
+
571
+
572
+class EditorNewSessionFieldset(poobrains.form.Fieldset):
573
+
574
+    handle = poobrains.form.fields.Text(required=True, validators=[validate_handle_not_in_session])
575
+    #new_session = poobrains.form.Button(label='Create session', type='submit')
576
+
577
+    def process(self, submit, instance):
578
+
579
+        app.debugger.set_trace()
580
+
581
+        handle = self.fields['handle'].value
582
+
583
+        session['editor-sessions'][handle] = {
584
+            'handle': handle,
585
+            'action': None,
586
+            'action_data': None,
587
+            'data': None
588
+        }
589
+
590
+        return redirect(DataEditor.url(handle=handle, mode='full'))
591
+
539 592
 
540 593
 class EditorLoadFieldset(poobrains.form.Fieldset):
541 594
 
542 595
     dataset = poobrains.form.fields.Select(choices=dataset_choices)
543
-    load_dataset = poobrains.form.Button(label="Load", type="submit")
596
+    load_dataset = poobrains.form.Button(label='Load', type='submit')
597
+
598
+
599
+    def __init__(self, editor_handle, **kwargs):
600
+
601
+        app.debugger.set_trace()
602
+        datasets = dynamic_datasets()
603
+
604
+        super(EditorLoadFieldset, self).__init__(**kwargs)
605
+
606
+        self.editor_handle = editor_handle
607
+        action = session['editor-sessions'][self.editor_handle]['action']
608
+
609
+        if action.startswith('load.dynamic.'):
610
+
611
+            dataset_name = action.split('.')[2]
612
+            self.fields['dataset'].value = '_%s' % dataset_name
613
+            self.fields['dataset'].readonly = True
614
+
615
+            if dataset_name in datasets:
616
+
617
+                dataset_class = datasets[dataset_name]
618
+                self.summon_parameter_fieldset(dataset_class)
619
+
620
+
621
+            else:
622
+                flash('Unknown dynamic dataset: %s' % dataset_name, 'error')
623
+
624
+    def summon_parameter_fieldset(self, dataset_class):
625
+
626
+            subform = poobrains.form.Fieldset(name='dynamic')
627
+            params = dynamic_dataset_parameters(dataset_class)
628
+
629
+            for param_name, param_type in params.items():
630
+                param_type = poobrains.form.types.lookup_table[param_type]()
631
+                subform.add_field(poobrains.form.fields.Text(name=param_name, label=param_name, type=param_type))
632
+
633
+            subform.cancel = poobrains.form.Button(type='submit', label='Cancel')
634
+
635
+            self.add_field(subform)
544 636
 
545 637
 
546 638
     def process(self, submit, instance):
547
-        if submit == '%s.%s' % (self.name, 'load_dataset'):
548
-            instance.dataset = load_dataset(self.fields['dataset'].value)
549
-            session['editor-sessions'][instance.handle_string] = instance.dataset.data.to_dict()
639
+
640
+        app.debugger.set_trace()
641
+
642
+        datasets = dynamic_datasets()
643
+        action = session['editor-sessions'][self.editor_handle]['action']
644
+        dataset_name = self.fields['dataset'].value
645
+        if action == 'load':
646
+    
647
+            if submit == '%s.load_dataset' % self.name:
648
+
649
+                if dataset_name.startswith('_'):
650
+
651
+                    dataset_name = dataset_name[1:]
652
+                    if not dataset_name in datasets:
653
+                        flash('Unknown dynamic dataset: %s' % dataset_name, 'error')
654
+                    else:
655
+                        session['editor-sessions'][instance.handle_string]['action'] = 'load.dynamic.%s' % dataset_name
656
+                        self.summon_parameter_fieldset(datasets[dataset_name])
657
+                        self.fields['dataset'].readonly = True
658
+                else: # directly loadable dataset
659
+
660
+                    try:
661
+                        dataset = StoredDataset.load(dataset_name)
662
+                        dataset.permissions['read'].check(g.user)
663
+                        instance.dataset = dataset
664
+                        session['editor-sessions'][instance.handle_string]['data'] = instance.dataset.data.to_dict()
665
+                        session['editor-sessions'][instance.handle_string]['action'] = None
666
+                    except StoredDataset.DoesNotExist:
667
+                        flash('Unknown stored dataset: %s' % dataset_name, 'error')
668
+
669
+        if action.startswith('load.dynamic.'):
670
+
671
+            if submit == '%s.dynamic.cancel' % self.name:
672
+                session['editor-sessions'][instance.handle_string]['action'] = 'load'
673
+                del(self.fields['dynamic']) # actively remove the fieldset so it doesn't get rendered in the response directly to this request
674
+                self.fields['dataset'].readonly = False
675
+
676
+            elif submit == '%s.load_dataset' % self.name:
677
+
678
+                dataset_name = dataset_name[1:]
679
+                dataset_class = datasets[dataset_name]
680
+                params = dynamic_dataset_parameters(dataset_class)
681
+
682
+                param_values = {}
683
+                for param_name in params:
684
+                    if param_name in self.fields['dynamic'].fields:
685
+                        param_values[param_name] = self.fields['dynamic'].fields[param_name].value
686
+
687
+                if len(param_values) == len(params):
688
+
689
+                    dataset = dataset_class()
690
+                    dataset.fill(**param_values)
691
+
692
+                    dataset.permissions['read'].check(g.user)
693
+                    instance.dataset = dataset
694
+                    session['editor-sessions'][instance.handle_string]['data'] = instance.dataset.data.to_dict()
695
+                    session['editor-sessions'][instance.handle_string]['action'] = None
696
+                    del(self.fields['dynamic'])
697
+                    self.fields['dataset'].readonly = False
698
+
699
+                else:
700
+                    flash('Incomplete parameters for dynamic dataset %s!' % dataset_name, 'error')
550 701
 
551 702
 
552 703
 @app.expose('/svg/plot/editor/', force_secure=True)
553 704
 class DataEditor(poobrains.auth.ProtectedForm):
554 705
 
555 706
     save_dataset = poobrains.form.Button(label="Save", type="submit")
556
-    
707
+
557 708
 
558 709
     def __init__(self, handle=None, mode=None, **kwargs):
559 710
 
711
+        app.debugger.set_trace()
560 712
         super(DataEditor, self).__init__(handle=handle, mode=mode, **kwargs)
561
-        
562
-        self.dataset = None
563
-        #self.fields['load_fieldset'] = EditorLoadFieldset(prefix=self.name, name='load_fieldset')
564
-        self.add_field(EditorLoadFieldset())
713
+ 
714
+        self.menu_actions = poobrains.rendering.Menu('data-editor-sessions', title='Editor sessions')
715
+        for editor_session_id in session['editor-sessions'].keys():
716
+            self.menu_actions.append(self.__class__.url(mode='full', handle=editor_session_id), editor_session_id)
565 717
 
566 718
 
567
-    @poobrains.helpers.themed
568
-    def view(self, mode='full', **kwargs):
719
+        if not self.handle_string or not self.handle_string in session['editor-sessions']: # new editor session
720
+            self.add_field(EditorNewSessionFieldset(self.handle_string))
569 721
 
570
-        if not self.handle_string:
571
-            return redirect(type(self)(handle=new_editor_handle()).url('full'))
722
+        else:
572 723
 
573
-        if self.handle_string in session:
574
-            #self.dataset = session[self.handle_string]
724
+            editor_session = session['editor-sessions'][self.handle_string]
575 725
             self.dataset = Dataset()
576
-            self.dataset.data = pandas.DataFrame.from_dict(session['editor-sessions'][self.handle_string])
726
+            self.dataset.data = pandas.DataFrame.from_dict(editor_session['data'])
727
+
728
+            if editor_session['action'] is None:
729
+                editor_session['action'] = 'load'
730
+
731
+            if editor_session['action'].startswith('load'):
732
+                self.add_field(EditorLoadFieldset(self.handle_string))
733
+
577 734
 
578
-        return poobrains.helpers.ThemedPassthrough(super(DataEditor, self).view(mode=mode, **kwargs))
735
+    #@poobrains.helpers.themed
736
+    #def view(self, mode='full', **kwargs):
737
+
738
+        #if not self.handle_string:
739
+        #    return redirect(type(self)(handle=new_editor_handle()).url('full'))
740
+
741
+        #if self.handle_string and self.handle_string in session:
742
+        #    #self.dataset = session[self.handle_string]
743
+        #    self.dataset = Dataset()
744
+        #    self.dataset.data = pandas.DataFrame.from_dict(session['editor-sessions'][self.handle_string])
745
+
746
+        #return poobrains.helpers.ThemedPassthrough(super(DataEditor, self).view(mode=mode, **kwargs))
579 747
 
580 748
 
581 749
     def process(self, submit):
582 750
 
583
-        if submit.startswith('EditorLoadFieldset.'):
751
+        app.debugger.set_trace()
752
+        if submit.startswith('EditorNewSessionFieldset.'):
753
+            return self.fields['EditorNewSessionFieldset'].process(submit, self)
754
+
755
+        elif submit.startswith('EditorLoadFieldset.'):
584 756
             self.fields['EditorLoadFieldset'].process(submit, self)
585 757
             #session[self.handle_string] = self.data
586 758
 

Loading…
Cancel
Save