## Copyright 2009-2013 Luc Saffre
## This file is part of the Lino project.
## Lino is free software; you can redistribute it and/or modify 
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
## Lino is distributed in the hope that it will be useful, 
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with Lino; if not, see <http://www.gnu.org/licenses/>.
"""

Defines the `Store` class and its fields 

"""

import logging
logger = logging.getLogger(__name__)

#~ import odf

import cgi
import datetime
#~ import decimal
#~ from dateutil import parser as dateparser

from django.conf import settings
from django.db import models
from django.db.models.fields import NOT_PROVIDED
from django.core import exceptions
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_unicode
from django.contrib.contenttypes import generic

from lino.utils import jsgen 
from lino.utils.jsgen import py2js, Component, id2js, js_code
from lino.utils.quantities import parse_decimal
#~ from lino.utils.xmlgen import etree
#~ from lino.utils.xmlgen.html import E

#~ from . import ext_requests
from lino.ui import requests as ext_requests

import lino
from lino.core import dbtables
from lino.core import fields
from lino.core import actions
from lino.core import frames
from lino import dd
#~ from lino.modlib.properties import models as properties
from lino.utils import choosers
from lino.utils import curry
from lino.utils import babel
from lino.utils import iif
from lino.core.modeltools import obj2str
from lino.core.requests import PhantomRow
from lino.utils import IncompleteDate
#~ from lino.core import tables
#~ from lino.utils import moneyfmt
from lino.mixins.printable import decfmt

from lino.utils.xmlgen import html as xghtml

class StoreField(object):
    """
    Base class for the fields of a :class:`Store`.
    
    Note: `value_from_object` and `full_value_from_object` are similar, 
    but for ForeignKeyStoreField one returns the primary key while the 
    other returns the full instance.
    
    """
    
    
    form2obj_default = None
    "because checkboxes are not submitted when they are off"
    
    list_values_count = 1
    "Necessary to compute :attr:`Store.pk_index`."
    
    
    def __init__(self,field,name,**options):
        self.field = field
        self.name = name
        #~ if isinstance(field,generic.GenericForeignKey):
            #~ self.editable = False
        #~ else:
            #~ self.editable = field.editable # VirtualField changes this
        #~ options.update(name=name or field.name)
        options.update(name=name)
        self.options = options
        
    #~ def __repr__(self):
        #~ return self.__class__.__name__ + ' ' + self.field.name
        
    def as_js(self):
        return py2js(self.options)
        
    def __repr__(self):
        return "%s '%s'" % (self.__class__.__name__, self.name)
        
    def column_names(self):
        yield self.options['name']
        
    def value_from_object(self,obj,ar):
        return self.full_value_from_object(obj,ar)
        
    def full_value_from_object(self,obj,ar):
        return self.field.value_from_object(obj)
    
        
    def value2list(self,ar,v,l,row):
        return l.append(v)
        
    def value2dict(self,ui,v,d,row):
        d[self.name] = v

    #~ def value2odt(self,ar,v,tc,**params):
        #~ """
        #~ Add the necessary :term:`odfpy` element(s) to the containing element `tc`.
        #~ """
        #~ params.update(text=force_unicode(v))
        #~ tc.addElement(odf.text.P(**params))
      
    def parse_form_value(self,v,obj):
        #~ if v == '' and not self.field.empty_strings_allowed:
            #~ return None
        return self.field.to_python(v)
        
    def extract_form_data(self,post_data):
        #~ logger.info("20130128 StoreField.extract_form_data %s",self.name)
        return post_data.get(self.name,None)
        #~ v = post_data.get(self.field.name,self.form2obj_default)
        #~ if v is None:
            #~ # means that the field wasn't part of the submitted form. don't touch it.
            #~ # except for checkboxes (who unfortunately are not submitted when clear)
            #~ # whose form2obj_default is False instead of None
            #~ return
    #~ v = post_data.get(self.field.name,NOT_PROVIDED)
    #~ if v is NOT_PROVIDED:
        #~ return
        
    def form2obj(self,ar,instance,post_data,is_new):
        """
        Test cases:
        - setting a CharField to ''
        - sales.Invoice.number may be blank        
        """
        v = self.extract_form_data(post_data)
        #~ logger.info("20130128 %s.form2obj() %s = %r",self.__class__.__name__,self.name,v)
        if v is None:
            # means that the field wasn't part of the submitted form. don't touch it.
            return
        if v == '':
            #~ print 20130125, self.field.empty_strings_allowed, self.field.name, self.form2obj_default
            if self.field.empty_strings_allowed:
                v = self.parse_form_value(v,instance)
                # if a field has been posted with empty string, 
                # we don't want it to get the field's default value! 
                # otherwise checkboxes with default value True can never be unset!
                # charfields have empty_strings_allowed
                # e.g. id field may be empty
                # but don't do this for other cases
            else:
                v = self.form2obj_default
        else:
            v = self.parse_form_value(v,instance)
        if not is_new and self.field.primary_key and instance.pk is not None:
            if instance.pk == v:
                return
            raise exceptions.ValidationError({
              self.field.name:_("Existing primary key value %r may not be modified.") % instance.pk})
              
        return self.set_value_in_object(ar,instance,v)
        
    def set_value_in_object(self,ar,instance,v):
        old_value = self.value_from_object(instance,ar.request)
        #~ old_value = getattr(instance,self.field.attname)
        if old_value != v:
            setattr(instance,self.name,v)
            return True
            
    def format_value(self,ar,v):
        """
        Return a plain textual representation of this value as a unicode string.
        """
        return force_unicode(v)
        

#~ class RemoteStoreField(StoreField):
    #~ def __init__(self,store,rf):
        #~ self.remote_field = rf
        #~ self.delegate = store.create_field(rf.field)
        #~ StoreField.__init__
      
class RelatedMixin(object):
        
    def get_rel_to(self,obj):
        #~ if self.field.rel is None:
            #~ return None
        return self.field.rel.to
        
    def full_value_from_object(self,obj,ar):
        # here we don't want the pk (stored in field's attname) 
        # but the full object this field refers to
        relto_model = self.get_rel_to(obj)
        if not relto_model:
            logger.warning("%s get_rel_to returned None",self.field)
            return None
        try:
            return getattr(obj,self.name)
        except relto_model.DoesNotExist,e:
            return None
        


class ComboStoreField(StoreField):
  
    list_values_count = 2
    
    def as_js(self):
        s = StoreField.as_js(self)
        #~ s += "," + repr(self.field.name+ext_requests.CHOICES_HIDDEN_SUFFIX)
        s += ", '%s'" % (self.options['name']+ext_requests.CHOICES_HIDDEN_SUFFIX)
        return s 
        
    def column_names(self):
        #~ yield self.options['name']
        #~ yield self.options['name'] + ext_requests.CHOICES_HIDDEN_SUFFIX
        yield self.name
        yield self.name + ext_requests.CHOICES_HIDDEN_SUFFIX
        
    def extract_form_data(self,post_data):
        #~ logger.info("20130128 ComboStoreField.extract_form_data %s",self.name)
        return post_data.get(self.name+ext_requests.CHOICES_HIDDEN_SUFFIX,None)
        
    #~ def obj2list(self,request,obj):
    def value2list(self,ar,v,l,row):
        value,text = self.get_value_text(v,row)
        l += [text,value]
        
    #~ def obj2dict(self,request,obj,d):
    def value2dict(self,request,v,d,row):
        value,text = self.get_value_text(v,row)
        d[self.name] = text
        d[self.name + ext_requests.CHOICES_HIDDEN_SUFFIX] = value
        
    def get_value_text(self,v,obj):
        #~ v = self.full_value_from_object(None,obj)
        if v is None or v == '':
            return (None, None)
        ch = choosers.get_for_field(self.field) 
        if ch is not None:
            return (v, ch.get_text_for_value(v,obj))
        for i in self.field.choices:
            if i[0] == v:
                return (v, unicode(i[1]))
        return (v, _("%r (invalid choice)") % v)
        
class ForeignKeyStoreField(RelatedMixin,ComboStoreField):
        
    #~ def cell_html(self,req,row):
        #~ obj = self.full_value_from_object(req,row)
        #~ if obj is None:
            #~ return ''
        #~ return req.ui.href_to(obj)
        
        
    def get_value_text(self,v,obj):
        #~ v = self.full_value_from_object(None,obj)
        #~ if isinstance(v,basestring):
            #~ logger.info("20120109 %s -> %s -> %r",obj,self,v)
        if v is None:
            return (None, None)
        else:
            return (v.pk, unicode(v))
            
    def parse_form_value(self,v,obj):
        relto_model = self.get_rel_to(obj)
        if not relto_model:
            #~ logger.info("20111209 get_value_text: no relto_model")
            return
        try:
            return relto_model.objects.get(pk=v)
        except ValueError,e:
            pass
        except relto_model.DoesNotExist,e:
            pass
            
        ch = choosers.get_for_field(self.field)
        #~ if ch and ch.meth.quick_insert_field:
        if ch and ch.can_create_choice:
            return ch.create_choice(obj,v)
            #~ if o is not None:
                #~ o.full_clean()
                #~ o.save()
                #~ logger.info(u"Auto-created %s %s",
                    #~ unicode(o._meta.verbose_name),unicode(o))
                #~ return o
        return None
        
            
#~ class LinkedForeignKeyField(ForeignKeyStoreField):
  
    #~ def get_rel_to(self,obj):
        #~ ct = self.field.get_content_type(obj)
        #~ if ct is None:
            #~ return None
        #~ return ct.model_class()
        


class VirtStoreField(StoreField):
  
    def __init__(self,vf,delegate,name):
        self.vf = vf
        StoreField.__init__(self,vf.return_type,name)
        self.as_js = delegate.as_js
        self.column_names = delegate.column_names
        self.list_values_count = delegate.list_values_count
        self.form2obj_default = delegate.form2obj_default
        #~ 20130130 self.value2num = delegate.value2num
        #~ 20130130 self.value2html = delegate.value2html
        self.value2list = delegate.value2list
        self.value2dict = delegate.value2dict
        self.format_value = delegate.format_value
        #~ 20130130 self.format_sum = delegate.format_sum
        #~ 20130130 self.sum2html = delegate.sum2html
        #~ self.form2obj = delegate.form2obj
        # as long as http://code.djangoproject.com/ticket/15497 is open:
        self.parse_form_value = delegate.parse_form_value
        self.set_value_in_object = vf.set_value_in_object
        #~ 20130130 self.apply_cell_format = delegate.apply_cell_format
        #~ self.value_from_object = vf.value_from_object
        
        self.delegate = delegate

    def __repr__(self):
        return '(virtual)' + self.delegate.__class__.__name__ + ' ' + self.name
        
    def full_value_from_object(self,obj,ar):
        return self.vf.value_from_object(obj,ar)

        
class RequestStoreField(StoreField):
    """
    StoreField for :class:`lino.core.fields.RequestField`.
    """
    def __init__(self,vf,delegate,name):
        self.vf = vf 
        StoreField.__init__(self,vf.return_type,name)
        #~ self.editable = False
        self.as_js = delegate.as_js
        self.column_names = delegate.column_names
        self.list_values_count = delegate.list_values_count
        
    def full_value_from_object(self,obj,ar):
        return self.vf.value_from_object(obj,ar)

    def value2list(self,ar,v,l,row):
        return l.append(self.format_value(ar,v))
        
    def value2dict(self,ui,v,d,row):
        d[self.name] = self.format_value(ui,v)
        #~ d[self.options['name']] = self.format_value(ui,v)
        #~ d[self.field.name] = v

        
    #~ def sum2html(self,ar,sums,i,**cellattrs):
        #~ cellattrs.update(align="right")
        #~ return super(RequestStoreField,self).sum2html(ar,sums,i,**cellattrs)
        
    #~ def value2odt(self,ar,v,tc,**params):
        #~ params.update(text=self.format_value(ar,v))
        #~ tc.addElement(odf.text.P(**params))





class PasswordStoreField(StoreField):
  
    def value_from_object(self,obj,request):
        v = super(PasswordStoreField,self).value_from_object(obj,request)
        if v:
            return "*" * len(v)
        return v
        
class GenericForeignKeyField(StoreField):
        
    def full_value_from_object(self,obj,ar):
        owner = getattr(obj,self.name)
        if owner is None: 
            #~ owner_id = getattr(obj,self.field.fk_field)
            #~ if owner_id is None:
                #~ return ''
            return ''
        return ar.href_to(owner)
  
class SpecialStoreField(StoreField):
    field = None 
    name = None
    editable = False
  
    def __init__(self,store):
        self.options = dict(name=self.name)
        self.store = store
        
    #~ def value2dict(self,ui,v,d):
        #~ d[self.name] = v

    #~ def obj2dict(self,request,obj,d):
        #~ # d.update(disable_editing=self.value_from_object(request,obj))
        #~ d[self.name] = self.value_from_object(request,obj)

    #~ def __repr__(self):
        #~ return "%s '%s'" % (self.__class__.__name__, self.name)
        
    def parse_form_value(self,v,instance):
        pass
        
    #~ def obj2list(self,request,obj):
        #~ return [self.value_from_object(request,obj)]
      
    #~ def value2list(self,ui,v):
        #~ return [v]
      
    def form2obj(self,ar,instance,post_data,is_new):
        pass
        #~ raise NotImplementedError
        #~ return instance

class DisabledFieldsStoreField(SpecialStoreField):
    """
    See also blog entries 20100803, 20111003, 20120901
    
    Note some special cases:
    
    - vat.VatDocument.total_incl (readonly virtual PriceField) must be disabled 
      and may not get submitted. 
      ExtJS requires us to set this dynamically each time.
    - JobsOverview.body (a virtual HtmlBox) 
      or Model.workflow_buttons (a displayfield) must *not* have the 'disabled' css class
    - 
    """
    name = 'disabled_fields'
    
    def __init__(self,store):
        SpecialStoreField.__init__(self,store)
        self.disabled_fields = set()
        for f in self.store.all_fields:
            if f.field is not None:
                if isinstance(f,VirtStoreField):
                    if not f.vf.editable:
                        if not isinstance(f.vf.return_type,dd.DisplayField):
                            self.disabled_fields.add(f.name)
                            #~ print "20121010 always disabled:", f
                elif not isinstance(f.field,generic.GenericForeignKey):
                    if not f.field.editable:
                        self.disabled_fields.add(f.name)
        
    def full_value_from_object(self,obj,ar):
        #~ l = [ f.name for f in self.store.actor.disabled_fields(request,obj)]
        d = dict()
        #~ if self.store.actor.disabled_fields is not None:
        for name in self.store.actor.disabled_fields(obj,ar):
            d[name] = True
        #~ l = list(self.store.actor.disabled_fields(obj,request))
        
        for name in self.disabled_fields:
            d[name] = True
        
        # disable the primary key field if pk is set (i.e. on saved instance):
        if self.store.pk is not None and obj.pk is not None:
            d[self.store.pk.attname] = True
            #~ l.append(self.store.pk.attname)
            # MTI children have two "primary keys":
            if isinstance(self.store.pk,models.OneToOneField):
                #~ l.append(self.store.pk.rel.field_name)
                d[self.store.pk.rel.field_name] = True
        return d
        
class DisabledActionsStoreField(SpecialStoreField):
    """
    """
    name = 'disabled_actions'
    
    def full_value_from_object(self,obj,ar):
        return self.store.actor.disabled_actions(ar,obj)
        
        
#~ class RecnoStoreField(SpecialStoreField):
    #~ name = 'recno'
    #~ def full_value_from_object(self,request,obj):
        #~ return 
        
class DisableEditingStoreField(SpecialStoreField):
    """
    A field whose value is the result of the `get_row_permission` 
    method on that row.
    New feature since :doc:`/blog/2011/0830`
    """
    name = 'disable_editing'
        
    def full_value_from_object(self,obj,ar):
        #~ return self.store.actor.disable_editing(obj,request)
        #~ a = self.store.actor.submit_action
        #~ m = getattr(obj,'get_permission',None)
        #~ u = ar.get_user()
        #~ if not self.store.actor.get_permission(a,u) or (m is not None and not m(a,u)):
        #~ if not self.store.actor.get_permission(a,u,obj):
        #~ return not self.store.actor.get_permission(actions.UPDATE,ar.get_user(),obj)
        #~ if self.store.actor.get_row_permission is None:
            #~ return self.store.actor.editable
        #~ return not self.store.actor.get_row_permission(actions.UPDATE,ar.get_user(),obj)
        actor = self.store.actor
        if actor.update_action is None:
            #~ print 20120601, self.store.actor, "update_action is None"
            return True # disable editing if there's no update_action
        #~ state = actor.get_row_state(obj)
        #~ print 20120621, obj.__class__
        #~ return not obj.get_row_permission(ar.get_user(),state,self.store.actor.update_action)
        #~ logger.info("20120622 ext_store gonna call get_action_permission")
        v = actor.get_row_permission(obj,ar,actor.get_row_state(obj),actor.update_action)
        #~ v = self.store.actor.update_action.get_action_permission(ar.get_user(),obj,state)
        #~ logger.info("ext_store 20120622 got %r", v)
        return not v
        #~ return not self.store.actor.get_row_permission(self.store.actor.update_action,ar.get_user(),obj)
        #~ if not self.store.actor.get_permission(actions.UPDATE,ar.get_user(),obj):
            #~ return True
        #~ return False
        #~ if not self.store.report.get_permission(ar.get_user(),):
            #~ return False
        

        
#~ class PropertiesStoreField(StoreField):
#~ class PropertyStoreField(StoreField):
    #~ def __init__(self,field,**kw):
        #~ kw['type'] = ...
        #~ StoreField.__init__(self,field,**kw)
    #~ def get_from_form(self,instance,post_data):
        #~ v = post_data.get(self.field.name)
        #~ if v == 'true':
            #~ v = True
        #~ else:
            #~ v = False
        #~ instance[self.field.name] = v

#~ from lino.utils.textfields import extract_summary

#~ class TextStoreField(StoreField):
  
    #~ def value_from_object(self,request,obj):
        #~ v = self.field.value_from_object(obj)
        #~ if request.expand_memos:
            #~ return v
        #~ return extract_summary(v)
  


class BooleanStoreField(StoreField):
    """
    This class wouldn't be necessary if Django's 
    `BooleanField.to_python` method would interpret 
    "on" as a valid `True` value. We had some interesting 
    discussion on this in :djangoticket:`#15497 (BooleanField
    should work for all PostgreSQL expressions)<15497>`. 
    """
  
    form2obj_default = False # 'off'
    
    def __init__(self,field,name,**kw):
        kw['type'] = 'boolean'
        StoreField.__init__(self,field,name,**kw)
        if not field.editable:
            def full_value_from_object(self,obj,ar):
                #~ return self.value2html(ar,self.field.value_from_object(obj))
                return self.format_value(ar,self.field.value_from_object(obj))
            self.full_value_from_object = curry(full_value_from_object,self)
        
        
    def parse_form_value(self,v,obj):
        """
        Ext.ensible CalendarPanel sends boolean values as 
        """
        #~ print "20110717 parse_form_value", self.field.name, v, obj
        return ext_requests.parse_boolean(v)

        
    def format_value(self,ar,v):
        return force_unicode(iif(v,_("Yes"),_("No")))
        
      
class DisplayStoreField(StoreField):
    pass
    #~ def value2html(self,ar,v,**cellattrs):
        #~ return E.td(v,**cellattrs)
  

class DecimalStoreField(StoreField):
    #~ def __init__(self,field,name,**kw):
        #~ kw['type'] = 'float'
        #~ StoreField.__init__(self,field,name,**kw)
        
    def parse_form_value(self,v,obj):
        return parse_decimal(v)

    #~ def value2num(self,v):
        #~ # print "20120426 %s value2num(%s)" % (self,v)
        #~ return v
        
    def format_value(self,ar,v):
        if not v:
            return ''
        return decfmt(v,places=self.field.decimal_places)
  
    #~ def value2html(self,ar,v,**cellattrs):
        #~ cellattrs.update(align="right")
        #~ return E.td(self.format_value(ar,v),**cellattrs)
        
        
class IntegerStoreField(StoreField):
    def __init__(self,field,name,**kw):
        kw['type'] = 'int'
        StoreField.__init__(self,field,name,**kw)
        
        
        
class AutoStoreField(StoreField):
    """
    A :class:`StoreField` for 
    `AutoField <https://docs.djangoproject.com/en/dev/ref/models/fields/#autofield>`__
    """
    def __init__(self,field,name,**kw):
        kw['type'] = 'int'
        StoreField.__init__(self,field,name,**kw)
  
    def form2obj(self,ar,obj,post_data,is_new):
        #~ logger.info("20121022 AutoStoreField.form2obj(%r)",ar.bound_action.full_name())
        if isinstance(ar.bound_action.action,actions.InsertRow):
            return super(AutoStoreField,self).form2obj(ar,obj,post_data,is_new)
        
        
        

class DateStoreField(StoreField):
  
    def __init__(self,field,name,**kw):
        kw['type'] = 'date'
        kw['dateFormat'] = settings.LINO.date_format_extjs # date_format # 'Y-m-d'
        StoreField.__init__(self,field,name,**kw)
        
    def parse_form_value(self,v,obj):
        if v:
            v = datetime.date(*settings.LINO.parse_date(v))
        else:
            v = None
        return v
        
    def format_value(self,ar,v):
        """
        Return a plain textual representation of this value as a unicode string.
        """
        return babel.dtos(v)
        #~ return settings.LINO.format_date(v)
        #~ return force_unicode(v)
        

class IncompleteDateStoreField(StoreField):
  
    def parse_form_value(self,v,obj):
        if v:
            v = IncompleteDate(*settings.LINO.parse_date(v))
            #~ v = datetime.date(*settings.LINO.parse_date(v))
        return v

class DateTimeStoreField(StoreField):
  
    def parse_form_value(self,v,obj):
        if v:
            return settings.LINO.parse_datetime(v) 
        return None

class TimeStoreField(StoreField):
  
    def parse_form_value(self,v,obj):
        if v:
            return settings.LINO.parse_time(v) 
        return None


class FileFieldStoreField(StoreField):
  
    def full_value_from_object(self,obj,request):
        ff = self.field.value_from_object(obj)
        return ff.name
        
class MethodStoreField(StoreField):
    "Deprecated. See :doc:`/blog/2012/0327`."
    def full_value_from_object(self,obj,request):
        unbound_meth = self.field._return_type_for_method
        assert unbound_meth.func_code.co_argcount >= 2, (self.name, unbound_meth.func_code.co_varnames)
        #~ print self.field.name
        return unbound_meth(obj,request)
        
    def value_from_object(self,obj,request):
        unbound_meth = self.field._return_type_for_method
        assert unbound_meth.func_code.co_argcount >= 2, (self.name, unbound_meth.func_code.co_varnames)
        #~ print self.field.name
        return unbound_meth(obj,request)
        
    #~ def obj2list(self,request,obj):
        #~ return [self.value_from_object(request,obj)]
        
    #~ def obj2dict(self,request,obj,d):
        #  logger.debug('MethodStoreField.obj2dict() %s',self.field.name)
        #~ d[self.field.name] = self.value_from_object(request,obj)
        
    #~ def get_from_form(self,instance,post_data):
        #~ pass
        
    def form2obj(self,request,instance,post_data,is_new):
        pass
        #~ return instance
        #raise Exception("Cannot update a virtual field")

#~ class ComputedColumnField(StoreField):
  
    #~ def value_from_object(self,ar,obj):
        #~ m = self.field.func
        #~ # assert m.func_code.co_argcount >= 2, (self.field.name, m.func_code.co_varnames)
        #~ # print self.field.name
        #~ return m(obj,ar)[0]
        
    #~ def form2obj(self,request,instance,post_data,is_new):
        #~ pass




#~ class SlaveSummaryField(MethodStoreField):
  
    #~ def obj2dict(self,request,obj,d):
        #~ meth = getattr(obj,self.field.name)
        #~ #logger.debug('MethodStoreField.obj2dict() %s',self.field.name)
        #~ d[self.field.name] = self.slave_report.()

class OneToOneStoreField(RelatedMixin,StoreField):
        
    def value_from_object(self,obj,request):
        v = self.full_value_from_object(obj,request)
        #~ try:
            #~ v = getattr(obj,self.field.name)
        #~ except self.field.rel.to.DoesNotExist,e:
            #~ v = None
        if v is None:
            return None
        return v.pk
      
    #~ def obj2list(self,request,obj):
        #~ return [self.value_from_object(request,obj)]
        
    #~ def obj2dict(self,request,obj,d):
        #~ d[self.field.name] = self.value_from_object(request,obj)
        

class BaseStore(object):
        
    def create_field(self,fld,name):
        if isinstance(fld,fields.RemoteField):
            """
            Hack: we create a StoreField based on the remote field,
            then modify its behaviour.
            """
            sf = self.create_field(fld.field,fld.name)
            def value_from_object(sf,obj,ar):
                m = fld.func
                return m(obj)
                
            def full_value_from_object(sf,obj,ar):
                #~ logger.info("20120406 %s.full_value_from_object(%s)",sf.name,sf)
                m = fld.func
                return m(obj)
                
            sf.value_from_object = curry(value_from_object,sf)
            sf.full_value_from_object = curry(full_value_from_object,sf)
            #~ sf.field = fld.field
            #~ sf.value2list = curry(value2list,sf)
            return sf
        #~ if isinstance(fld,tables.ComputedColumn):
            #~ logger.info("20111230 Store.create_field(%s)", fld)
            #~ return ComputedColumnField(fld)
        meth = getattr(fld,'_return_type_for_method',None)
        if meth is not None:
            # uh, this is tricky...
            return MethodStoreField(fld,name)
        #~ if isinstance(fld,fields.HtmlBox):
            #~ ...
        #~ if isinstance(fld,dd.LinkedForeignKey):
            #~ return LinkedForeignKeyField(fld,name)
        if isinstance(fld,dd.RequestField):
            delegate = self.create_field(fld.return_type,fld.name)
            return RequestStoreField(fld,delegate,name)
        if isinstance(fld,dd.VirtualField):
            delegate = self.create_field(fld.return_type,fld.name)
            return VirtStoreField(fld,delegate,name)
        if isinstance(fld,models.FileField):
            return FileFieldStoreField(fld,name)
        if isinstance(fld,models.ManyToManyField):
            return StoreField(fld,name)
        if isinstance(fld,dd.PasswordField):
            return PasswordStoreField(fld,name)
        if isinstance(fld,models.OneToOneField):
            return OneToOneStoreField(fld,name)
        if isinstance(fld,generic.GenericForeignKey):
            return GenericForeignKeyField(fld,name)
        if isinstance(fld,dd.GenericForeignKeyIdField):
            return ComboStoreField(fld,name)
        if isinstance(fld,models.ForeignKey):
            return ForeignKeyStoreField(fld,name)
        if isinstance(fld,models.TimeField):
            return TimeStoreField(fld,name)
        if isinstance(fld,models.DateTimeField):
            return DateTimeStoreField(fld,name)
        if isinstance(fld,dd.IncompleteDateField):
            return IncompleteDateStoreField(fld,name)
        if isinstance(fld,models.DateField):
            return DateStoreField(fld,name)
        if isinstance(fld,models.BooleanField):
            return BooleanStoreField(fld,name)
        if isinstance(fld,models.DecimalField):
            return DecimalStoreField(fld,name)
        if isinstance(fld,models.AutoField):
            return AutoStoreField(fld,name)
            #~ kw.update(type='int')
        if isinstance(fld,models.SmallIntegerField):
            return IntegerStoreField(fld,name)
        if isinstance(fld,dd.DisplayField):
            return DisplayStoreField(fld,name)
        if isinstance(fld,models.IntegerField):
            return IntegerStoreField(fld,name)
        kw = {}
        if choosers.uses_simple_values(fld):
            return StoreField(fld,name,**kw)
        else:
            return ComboStoreField(fld,name,**kw)

class ParameterStore(BaseStore):
        
    def __init__(self,params_layout_handle,url_param):
        self.param_fields = []
        
        for pf in params_layout_handle._store_fields:
        #~ for pf in rh.report.params:
            self.param_fields.append(self.create_field(pf,pf.name))
        
        self.param_fields = tuple(self.param_fields)
        self.url_param = url_param
        self.params_layout_handle = params_layout_handle
        
    def __str__(self):
        return "%s of %s" % (self.__class__,self.params_layout_handle)

        
    def unused_pv2list(self,pv):
        l = []
        for f in self.param_fields:
            l.append(pv[f.field.name])
        return l
        
    def pv2dict(self,ui,pv,**d):
        for fld in self.param_fields:
            v = pv.get(fld.name,None)
            fld.value2dict(ui,v,d,None)
        return d

    def parse_params(self,request,**kw):
        pv = request.REQUEST.getlist(self.url_param)
        #~ logger.info("20120221 ParameterStore.parse_params(%s) --> %s",self.url_param,pv)
        def parse(sf,form_value):
            if form_value == '' and not sf.field.empty_strings_allowed:
                return sf.form2obj_default
                # if a field has been posted with empty string, 
                # we don't want it to get the field's default value because
                # otherwise checkboxes with default value True can never be unset.
                # charfields have empty_strings_allowed
                # e.g. id field may be empty
                # but don't do this for other cases
            else:
                return sf.parse_form_value(form_value,None)
      
        #~ if pv: 
            #~ if len(self.param_fields) != len(pv):
                #~ raise Exception("len(%r) != len(%r)" % (self.param_fields,pv))
        if len(pv) > 0:
            if len(self.param_fields) != len(pv):
                #~ logger.info('20121016 %s para_fields are %s',self,[sf.field.name for sf in self.param_fields])
                raise Exception("Expected a list of %d values, but got %s" % (len(self.param_fields),pv))
            for i,f in enumerate(self.param_fields):
                kw[f.field.name] = parse(f,pv[i])
        #~ else: removed 20120918
            #~ for i,f in enumerate(self.param_fields):
                #~ kw[f.field.name] = parse(f,'')
        #~ logger.info("20120221 2 parse_params(%s) --> %r",pv,kw)
        return kw
        



class Store(BaseStore):
    """
    
    Represents an :extjs:`Ext.data.JsonStore`.
    
    """
    
    pk = None
    
    def __init__(self,rh,**options):
        #~ assert isinstance(rh,tables.TableHandle)
        #~ Component.__init__(self,id2js(rh.report.actor_id),**options)
        self.rh = rh
        self.actor = rh.actor
        """
        MTI children have two primary keys. Example::
        >>> from lino.projects.pcsw.models import Person
        >>> [f for f in Person._meta.fields if f.primary_key]
        [<django.db.models.fields.AutoField: id>, <django.db.models.fields.related.OneToOneField: contact_ptr>]
        >>> Person._meta.pk
        <django.db.models.fields.related.OneToOneField: contact_ptr>
        >>> p = Person.objects.get(pk=118)
        >>> p
        <Person: ARENS Annette (118)>
        >>> p.contact_ptr_id = 117
        >>> print p.pk
        117
        >>> p.save()
        >>>        
        """
        #~ if issubclass(rh.report,dbtables.Table):
            #~ self.pk = self.report.model._meta.pk
            #~ assert self.pk is not None, "Cannot make Store for %s because %s has no pk" % (
              #~ self.report.actor_id,self.report.model)
          
        #~ fields = []
        
        self.df2sf = {} # temporary dict used by collect_fields and add_field_for
        self.all_fields = []
        self.list_fields = []
        self.detail_fields = []
        
        def addfield(sf):
            self.all_fields.append(sf)
            self.list_fields.append(sf)
            self.detail_fields.append(sf)
            
        if not issubclass(rh.actor,frames.Frame):
            self.collect_fields(self.list_fields,rh.get_list_layout())
            
        form = rh.actor.detail_layout
        if form:
            dh = form.get_layout_handle(rh.ui)
            self.collect_fields(self.detail_fields,dh)
            
        form = rh.actor.insert_layout
        if form:
            dh = form.get_layout_handle(rh.ui)
            self.collect_fields(self.detail_fields,dh)
        
        if issubclass(rh.actor,dbtables.Table):
            self.pk_index = 0
            found = False
            for fld in self.list_fields:
                """
                Django's Field.__cmp__() does::
                
                  return cmp(self.creation_counter, other.creation_counter) 
                  
                which causes an exception when trying to compare a field with an object of other type.
                """
                #~ if type(fld.field) == type(self.pk) and fld.field == self.pk:
                if (fld.field.__class__ is self.pk.__class__) and fld.field == self.pk:
                    #~ self.pk = fld.field
                    found = True
                    break
                self.pk_index += fld.list_values_count
            if not found:
                raise Exception("Primary key %s not found in list_fields %s" % (self.pk,self.list_fields))
            
        del self.df2sf
        
        #~ if not issubclass(rh.report,dbtables.Table):
            #~ addfield(RecnoStoreField(self))
          
        #~ if rh.report.disabled_fields is not None:
        addfield(DisabledFieldsStoreField(self))
        addfield(DisabledActionsStoreField(self))
            
            #~ sf = DisabledFieldsStoreField(self)
            #~ self.fields.append(sf)
            #~ self.list_fields.append(sf)
            #~ self.detail_fields.append(sf)
        #~ if rh.report.disable_editing is not None:
        #~ if rh.report.submit_action is not None:
        if rh.actor.editable:
            addfield(DisableEditingStoreField(self))
            
        #~ self.fields.append(PropertiesStoreField)
        #~ self.fields_dict = dict([(f.field.name,f) for f in self.fields])
        
        # virtual fields must come last so that Store.form2obj() 
        # processes "real" fields first.
        self.all_fields = [
            f for f in self.all_fields if not isinstance(f,VirtStoreField)
            ] + [
            f for f in self.all_fields if isinstance(f,VirtStoreField)
            ]
        #~ if str(self.report) == "cal.PanelEvents":
            #~ logger.info("20120119 %s",self.all_fields)
        self.all_fields = tuple(self.all_fields)
        self.list_fields = tuple(self.list_fields)
        self.detail_fields = tuple(self.detail_fields)
        

    def add_field_for(self,fields,df):
        sf = getattr(df,'_lino_atomizer',None)
        if sf is None:
            sf = self.create_field(df,df.name)
            setattr(df,'_lino_atomizer',sf)
            
        if not sf in self.all_fields:
            self.all_fields.append(sf)
            
        #~ sf = self.df2sf.get(df,None)
        #~ if sf is None:
            #~ sf = self.create_field(df,df.name)
            #~ self.all_fields.append(sf)
            #~ self.df2sf[df] = sf
        fields.append(sf)
        
    def collect_fields(self,fields,*layouts):
        """
        `fields` is a pointer to either `self.detail_fields` or `self.list_fields`.
        Each of these must contain a primary key field.
        
        """
        pk_found = False
        for layout in layouts:
            for df in layout._store_fields:
                assert df is not None
                self.add_field_for(fields,df)
                if df.primary_key:
                    pk_found = True
                    if self.pk is None:
                        self.pk = df
                #~ fields.add(fld)
        #~ if not self.pk in fields:
        if issubclass(self.actor,dbtables.Table):
            if self.pk is None:
                self.pk = self.actor.model._meta.pk
            if not pk_found:
                self.add_field_for(fields,self.pk)
                

    def form2obj(self,ar,form_values,instance,is_new):
        disabled_fields = set(self.actor.disabled_fields(instance,ar))
        #~ logger.info("20130107 Store.form2obj(%s)", form_values)
        #~ logger.info("20120228 %s Store.form2obj(%s),\ndisabled %s\n all_fields %s", 
            #~ self.rh,form_values,disabled_fields,self.all_fields)
        #~ print 20110406, disabled_fields
        changed_triggers = []
        for f in self.all_fields:
          #~ if f.field is not None and f.field.editable:
          #~ if f.editable:
            if not f.name in disabled_fields:
                try:
                    #~ if f.form2obj(ar.request,instance,form_values,is_new):
                    if f.form2obj(ar,instance,form_values,is_new):
                        #~ logger.info("20130107 Store.form2obj %s changed", f)
                        m = getattr(instance,f.name + "_changed",None)
                        if m is not None:
                            changed_triggers.append(m)
                except exceptions.ValidationError,e:
                    raise exceptions.ValidationError({f.name:e})
                except Exception,e:
                    logger.warning("Exception during Store.form2obj (field %s) : %s", f.name,e)
                    logger.exception(e)
                    raise 
                #~ logger.info("20120228 Store.form2obj %s -> %s", f, obj2str(instance))
        for m in changed_triggers:
            m(ar)
            #~ m()
            
        #~ m = getattr(instance,"_changed",None)
        #~ if m is not None:
            #~ m(request)
        #~ return instance
        #~ logger.info("20120603 Store.form2obj %s", instance.national_id)
            
    def column_names(self):
        l = []
        for fld in self.list_fields:
            l += fld.column_names()
        return l
        
    def column_index(self,name):
        """
        Used to set `disabled_actions_index`.
        Was used to write definition of Ext.ensible.cal.CalendarMappings
        and Ext.ensible.cal.EventMappings 
        """
        #~ logger.info("20111214 column_names: %s",list(self.column_names()))
        return list(self.column_names()).index(name)
        

    def row2list(self,request,row):
        #~ assert isinstance(request,dbtables.AbstractTableRequest)
        #~ if not isinstance(request,dbtables.ListActionRequest):
            #~ raise Exception()
        #~ logger.info("20120107 Store %s row2list(%s)", self.report.model, obj2str(row))
        l = []
        if isinstance(row,PhantomRow):
            for fld in self.list_fields:
                fld.value2list(request,None,l,row)
        #~ elif isinstance(row,actions.VirtualRow):
            #~ for fld in self.list_fields:
                #~ fld.value2list(request,None,l,row)
        else:
            for fld in self.list_fields:
                v = fld.full_value_from_object(row,request)
                #~ if fld.name == 'person__age':
                    #~ logger.info("20120406 Store.row2list %s -> %s", fld, v)
                fld.value2list(request,v,l,row)
                #~ if fld.name == 'person__age':
                    #~ logger.info("20120406 Store.row2list %s -> %s", fld, l)
        return l
      
    def row2dict(self,ar,row,fields=None,**d):
        #~ assert isinstance(ar,dbtables.AbstractTableRequest)
        #~ logger.info("20111209 Store.row2dict(%s)", obj2str(row))
        if fields is None:
            fields = self.detail_fields
        for fld in fields:
            #~ logger.info("20111209 Store.row2dict %s %s", row,fld)
            v = fld.full_value_from_object(row,ar)
            fld.value2dict(ar.ui,v,d,row)
            #~ logger.info("20111209 Store.row2dict %s -> %s", f, d)
        return d

    #~ def row2odt(self,request,fields,row,sums):
        #~ for i,fld in enumerate(fields):
            #~ if fld.field is not None:
                #~ v = fld.full_value_from_object(request,row)
                #~ if v is None:
                    #~ yield ''
                #~ else:
                    #~ sums[i] += fld.value2num(v)
                    #~ yield fld.value2odt(request,v)
            
            
