## Copyright 2009-2012 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 . import ext_requests
from lino.ui import requests as ext_requests

import lino
from lino.core import table
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.utils import IncompleteDate
from lino.utils 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.
    
    """
    
    zero = 0
    
    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
        #~ 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 value2num(self,v):
        #~ print "20120426 %s value2num(%s)" % (self,v)
        return 0
        
    def sum2html(self,ui,v):
        if not v:
            return ''
        return str(v)
        #~ return self.format_sum(v)
        
    def format_sum(self,ar,sums,i):
        return ''
        
    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 value2html(self,ar,v):
        """
        Return a HTML representation of the given value. 
        The possible return values may be:
        
        - a basestring (of which any special characters will be escaped)
        - an xml.etree.ElementTree.Element
        
        The default implementation returns a unicode string obtained from :meth:`format_value`.
        """
        #~ return "<span>%s</span>" % force_unicode(v)
        return self.format_value(ar,v)
      
    def format_value(self,ar,v):
        """
        Return a plain textual representation of this value as a unicode string.
        """
        return force_unicode(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):
        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,request,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("20120603 StoreField.form2obj() %s = %r",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 == '' and not self.field.empty_strings_allowed:
            v = self.form2obj_default
            # 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.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(request,instance,v)
        
    def set_value_in_object(self,request,instance,v):
        old_value = self.value_from_object(instance,request)
        #~ old_value = getattr(instance,self.field.attname)
        if old_value != v:
            setattr(instance,self.name,v)
            return True

#~ 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 += "," + repr(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):
        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 value2html(self,ar,v):
        h = ar.renderer.instance_handler(ar,v)
        if h is None:
            #~ return cgi.escape(force_unicode(obj))
            #~ return xghtml.E.p(xghtml.E.b(cgi.escape(force_unicode(v))))
            return xghtml.E.p(xghtml.E.b(force_unicode(v)))
        url = 'javascript:' + h
        #~ url = ar.renderer.js2url(h)
        #~ return self.href(url,text or cgi.escape(force_unicode(obj)))
        return xghtml.E.p(xghtml.E.a(force_unicode(v),href=url))
        
        #~ xml = ar.renderer.href_to(v)
        #~ # Python 2.6 : encode() takes no keyword arguments
        #~ # xml = xml.encode('utf-8',errors='xmlcharrefreplace')
        #~ xml = xml.encode('utf-8','xmlcharrefreplace')
        #~ return xghtml.RAW(xml)
        
        
    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:
            o = ch.create_choice(obj,v)
            if o is not None:
                logger.info(u"Auto-created %s %s",
                    unicode(o._meta.verbose_name),unicode(o))
                return o
            #~ qs = ch.get_instance_choices(obj)
            #~ print 20110425, qs
            #~ kw = {}
            #~ kw[ch.meth.quick_insert_field] = v
            #~ fk_target = qs.create(**kw)
            # fk_target.save() not necessary
            #~ logger.info("Auto-created %s %s",fk_target.__class__,fk_target)
            #~ return fk_target
            #~ return ch.on_quick_insert(obj,self.field,v)
        #~ else:
            #~ logger.info("Could not find %s#%s",relto_model,v)
        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
        self.value2num = delegate.value2num
        self.value2html = delegate.value2html
        self.value2list = delegate.value2list
        self.value2dict = delegate.value2dict
        self.format_value = delegate.format_value
        self.format_sum = delegate.format_sum
        #~ 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
        #~ self.value_from_object = vf.value_from_object
        
        self.delegate = delegate

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

        
class RequestStoreField(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
        #~ self.value2num = delegate.value2num
        #~ self.value2html = delegate.value2html
        #~ self.value2list = delegate.value2list
        #~ self.value2dict = delegate.value2dict
        # 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
        
    def full_value_from_object(self,obj,ar):
        return self.vf.value_from_object(obj,ar)

    def value2num(self,v):
        #~ return len(v.data_iterator)
        return v.get_total_count()
        
    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 value2html(self,ar,v):
        n = v.get_total_count()
        if n == 0:
            return ''
        #~ return ar.renderer.href_to_request(v,str(n))
        url = 'javascript:' + ar.renderer.request_handler(v)
        #~ url = ar.renderer.js2url(h)
        #~ return xghtml.E.a(cgi.escape(force_unicode(v.label)),href=url)
        return xghtml.E.p(xghtml.E.a(str(n),href=url))
      
        #~ s = self.format_value(ar,v)
        #~ if not s: return s
        #~ return xghtml.RAW(s)

    #~ def value2odt(self,ar,v,tc,**params):
        #~ params.update(text=self.format_value(ar,v))
        #~ tc.addElement(odf.text.P(**params))
        
    def format_sum(self,ar,sums,i):
        #~ return self.format_value(ar,sums[i])
        return str(sums[i])

        
    def format_value(self,ar,v):
        if v is None:
            raise Exception("Got None value for %s" % self)
        n = v.get_total_count()
        if n == 0:
            return ''
        return ar.renderer.href_to_request(v,str(n))







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: return ''
        return ar.href_to(owner)
  
class SpecialStoreField(StoreField):
    field = None 
    name = None
  
    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,request,instance,post_data,is_new):
        pass
        #~ raise NotImplementedError
        #~ return instance

class DisabledFieldsStoreField(SpecialStoreField):
    """
    See :doc:`/blog/2010/0803`,
    :doc:`/blog/2011/1003`
    """
    name = 'disabled_fields'
    
    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))
        # disable also the primary key field
        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.get_user(),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 DecimalStoreField(StoreField):
    zero = decimal.Decimal(0)
    def __init__(self,field,name,**kw):
        kw['type'] = 'float'
        StoreField.__init__(self,field,name,**kw)
        
    def parse_form_value(self,v,obj):
        if '.' in v and ',' in v:
            raise Exception("Invalid decimal value %r" % v)
        return v.replace(',','.')

    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 format_sum(self,ar,sums,i):
        return self.format_value(ar,sums[i])
        

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))
            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 value2html(self,ar,v):
    def format_value(self,ar,v):
        return force_unicode(iif(v,_("Yes"),_("No")))
        
    def value2num(self,v):
        if v: return 1
        return 0
      

class DisplayStoreField(StoreField):
  
    def value2html(self,ar,v):
        return v
  
class IntegerStoreField(StoreField):
    def __init__(self,field,name,**kw):
        kw['type'] = 'int'
        StoreField.__init__(self,field,name,**kw)
        
    def value2num(self,v):
        return v
        
        
        
class AutoStoreField(StoreField):
    def __init__(self,field,name,**kw):
        kw['type'] = 'int'
        StoreField.__init__(self,field,name,**kw)
  
    def form2obj(self,request,instance,post_data,is_new):
        pass
        
        
        

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 unused_form2obj(self,instance,post_data,is_new):
        #v = values.get(self.field.name,None)
        v = post_data.get(self.field.name,None)
        logger.info("OneToOneStoreField %s = %r",self.field.name,v)
        if v == '' and self.field.null:
            v = None
        if v is not None:
            v = self.field.rel.to.objects.get(pk=v)
        setattr(instance,self.field.name,v)
        #~ return instance
        
    #~ def get_from_form(self,instance,post_data):
        #~ #v = values.get(self.field.name,None)
        #~ v = post_data.get(self.field.name)
        #~ if v == '' and self.field.null:
            #~ v = None
        #~ if v is not None:
            #~ v = self.field.rel.to.objects.get(pk=v)
        #~ instance[self.field.name] = v
        
    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 Store:
    """
    
    Represents an :extjs:`Ext.data.JsonStore`.
    
    """
    
    pk = None
    
    #declare_type = jsgen.DECLARE_THIS
    #~ declare_type = jsgen.DECLARE_VAR
    #~ declare_type = jsgen.DECLARE_INLINE
    #~ ext_suffix = "_store"
    #~ value_template = "new Ext.data.JsonStore(%s)"
    
    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.apps.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,table.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,table.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
        
        self.param_fields = []
        
        if rh.actor.parameters:
            for pf in rh.params_layout._store_fields:
            #~ for pf in rh.report.params:
                self.param_fields.append(self.create_field(pf,pf.name))
        
        #~ if not issubclass(rh.report,table.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)
        self.param_fields = tuple(self.param_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,table.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 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):
            #~ logger.info("Store.create_field(%s)", fld)
            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)

    def form2obj(self,ar,form_values,instance,is_new):
        #~ raise Exception('20120211')
        if self.actor.disabled_fields:
            disabled_fields = set(self.actor.disabled_fields(instance,ar))
        else:
            disabled_fields = set()
        #~ logger.info("20120228 %s Store.form2obj(%s),\ndisabled %s\n all_fields %s", 
            #~ self.report,form_values,disabled_fields,self.all_fields)
        #~ print 20110406, disabled_fields
        changed_triggers = []
        for f in self.all_fields:
            #~ if f.field is None or not f.field.name in disabled_fields:
            if not f.name in disabled_fields:
                try:
                    if f.form2obj(ar.request,instance,form_values,is_new):
                        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("%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,table.AbstractTableRequest)
        #~ if not isinstance(request,table.ListActionRequest):
            #~ raise Exception()
        #~ logger.info("20120107 Store %s row2list(%s)", self.report.model, obj2str(row))
        l = []
        if isinstance(row,actions.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,table.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 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 row2html(self,ar,fields,row,sums):
        #~ for i,fld in enumerate(self.list_fields):
        for i,fld in enumerate(fields):
            #~ print 20120115, fld.field.name
            #~ if not isinstance(fld,SpecialStoreField):
            if fld.field is not None:
                #~ if fld.name == 'person__gsm':
                #~ logger.info("20120406 Store.row2list %s -> %s", fld, fld.field)
                #~ import pdb; pdb.set_trace()
                v = fld.full_value_from_object(row,ar)
                if v is None:
                    yield ''
                else:
                    sums[i] += fld.value2num(v)
                    #~ yield fld.value2html(ar,v)
                    x = fld.value2html(ar,v)
                    #~ from django.utils.functional import Promise
                    #~ if isinstance(x,Promise):
                        #~ print 20120614, fld.field
                        #~ raise Exception("20120614")
                    yield x
                #~ yield fld.cell_html(ar,row)
                
    def sums2html(self,ar,fields,sums):
        return [fld.format_sum(ar,sums,i)
          for i,fld in enumerate(fields)]
      
        #~ return [fld.sum2html(ar.ui,sums[i])
          #~ for i,fld in enumerate(fields)]
            
    #~ 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)
            
            
    def parse_params(self,request,**kw):
        pv = request.REQUEST.getlist(ext_requests.URL_PARAM_PARAM_VALUES)
        #~ logger.info("20120221 parse_params(%s)",pv)
        def parse(self,form_value):
            if form_value == '' and not self.field.empty_strings_allowed:
                return self.form2obj_default
                # 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:
                return self.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 pv and len(self.param_fields) == len(pv):
            for i,f in enumerate(self.param_fields):
                kw[f.field.name] = parse(f,pv[i])
        else:
            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
        
