# -*- coding: UTF-8 -*-
## Copyright 2008-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 classes :class:`Frame` and :class:`FrameHandle`
"""


from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.contrib.contenttypes import generic

import datetime

from decimal import Decimal


import logging
logger = logging.getLogger(__name__)

from lino.core.modeltools import full_model_name
from lino.core.modeltools import obj2str
from lino.core.modeltools import resolve_field
from lino.core.modeltools import resolve_model, UnresolvedModel
from lino.core.modeltools import is_installed_model_spec

#~ from lino.utils import choosers
from lino.utils import IncompleteDate, d2iso
#~ from lino.utils.quantities import Duration
from lino.utils import quantities 
from lino.utils import get_class_attr


class PasswordField(models.CharField):
    """Stored as plain text in database, but not displayed in user interface."""
    pass
    
    
#~ TEXT_FORMAT_PLAIN = 'plain'
#~ TEXT_FORMAT_HTML = 'html'
#~ TEXT_FORMAT_TINYMCE = 'tinymce'
#~ TEXT_FORMAT_VINYLFOX = 'vinylfox'
    

class RichTextField(models.TextField):
    """
    Only difference with Django's `models.TextField` is that you can 
    specify a keyword argument `format` to 
    override the global :attr:`lino.Lino.textfield_format`.
    """
    def __init__(self,*args,**kw):
        self.textfield_format = kw.pop('format',None)
        super(RichTextField,self).__init__(*args,**kw)
        
    def set_format(self,fmt):
        self.textfield_format = fmt
    
  
#~ class PercentageField(models.SmallIntegerField):
    #~ """
    #~ Deserves more documentation.
    #~ """
    #~ def __init__(self, *args, **kw):
        #~ defaults = dict(
            #~ max_length=3,
            #~ )
        #~ defaults.update(kw)
        #~ models.SmallIntegerField.__init__(self,*args, **defaults)

class PercentageField(models.DecimalField):
    """
    A field to express a percentage. 
    The database stores this like a DecimalField.
    Plain HTML adds a "%".
    """
    def __init__(self, *args, **kwargs):
        defaults = dict(
            max_length=5,
            max_digits=5,
            decimal_places=2,
            )
        defaults.update(kwargs)
        super(PercentageField, self).__init__(*args, **defaults)
  
class DatePickerField(models.DateField):
    """
    A DateField that uses a DatePicker instead of a normal DateWidget.
    Doesn't yet work.
    """

class MonthField(models.DateField):
    """
    A DateField that uses a MonthPicker instead of a normal DateWidget
    """
    def __init__(self, *args, **kw):
        models.DateField.__init__(self,*args, **kw)
  
class PriceField(models.DecimalField):
    """
    Deserves more documentation.
    """
    def __init__(self, *args, **kwargs):
        defaults = dict(
            max_length=10,
            max_digits=10,
            decimal_places=2,
            )
        defaults.update(kwargs)
        super(PriceField, self).__init__(*args, **defaults)
        
    #~ def formfield(self, **kwargs):
        #~ fld = super(PriceField, self).formfield(**kwargs)
        #~ # display size is smaller than full size:
        #~ fld.widget.attrs['size'] = "6"
        #~ fld.widget.attrs['style'] = "text-align:right;"
        #~ return fld
        
#~ class MyDateField(models.DateField):
        
    #~ def formfield(self, **kwargs):
        #~ fld = super(MyDateField, self).formfield(**kwargs)
        #~ # display size is smaller than full size:
        #~ fld.widget.attrs['size'] = "8"
        #~ return fld
        
        
"""
http://stackoverflow.com/questions/454436/unique-fields-that-allow-nulls-in-django
answer Dec 20 '09 at 3:40 by mightyhal
http://stackoverflow.com/a/1934764
"""
class NullCharField(models.CharField): #subclass the CharField
    description = "CharField that stores empty strings as NULL instead of ''."
    def __init__(self, *args, **kwargs):
        defaults = dict(blank=True,null=True)
        defaults.update(kwargs)
        super(NullCharField, self).__init__(*args, **defaults)
        
    def to_python(self, value):  #this is the value right out of the db, or an instance
       #~ if isinstance(value, models.CharField): #if an instance, just return the instance
       if isinstance(value, basestring): #if a string, just return the value
            return value 
       if value is None:   #if the db has a NULL (==None in Python)
            return ''  # convert it into the Django-friendly '' string
       else:
            return value # otherwise, return just the value
            
    def get_db_prep_value(self, value, connection, prepared=False):
    #catches value right before sending to db
       if value == '':     # if Django tries to save '' string, send the db None (NULL)
            return None
       else:
            return value # otherwise, just pass the value        

class FakeField(object):
    """
    Base class for 
    :class:`RemoteField`
    :class:`DisplayField`
    """
    choices = None
    primary_key = False
    editable = False
    name = None
    help_text = None
    
    def is_enabled(self,lh):
        """
        Overridden by mti.EnableChild
        """
        return False
        #~ return True
        #~ return not self.editable
        
    def has_default(self):
        return False
        

#~ class NullField(FakeField):
    #~ def __init__(self,name):
        #~ self.name = name
    
class RemoteField(FakeField):
    """
    Represents a field on a related object.
    LayoutHandle instantiates a RemoteField for example when 
    """
    #~ primary_key = False
    #~ editable = False
    def __init__(self,func,name,fld,**kw):
        self.func = func
        self.name = name
        self.field = fld
        self.rel = self.field.rel
        self.verbose_name = fld.verbose_name
        self.max_length = getattr(fld,'max_length',None)
        #~ print 20120424, self.name

    def value_from_object(self,obj,ar=None):
        """
        Return the value of this field in the specified model instance `obj`.
        `ar` may be `None`, it's forwarded to the getter method who may 
        decide to return values depending on it.
        """
        m = self.func
        return m(obj,ar)
        
    def __get__(self,instance,owner):
        if instance is None: return self
        return self.value_from_object(instance)

        

class DisplayField(FakeField):
    """
    Deserves more documentation.
    """
    choices = None
    blank = True
    drop_zone = None
    max_length = None
    #~ bbar = None
    def __init__(self,verbose_name=None,**kw):
        self.verbose_name = verbose_name
        for k,v in kw.items():
            assert hasattr(self,k)
            setattr(self,k,v)
    # the following dummy methods are never called but needed when using a DisplayField 
    # as return_type of a VirtualField
    def to_python(self,*args,**kw): 
        raise NotImplementedError("%s.to_python(%s,%s)",(self.name,args,kw))
    def save_form_data(self,*args,**kw): raise NotImplementedError
    def value_to_string(self,*args,**kw): raise NotImplementedError
        
    def value_from_object(self,obj,ar=None):
        return ''

#~ class DynamicForeignKey(DisplayField):
    #~ """
    #~ A pointer to "the" one and only MTI child.
    #~ This assumes that there is always one and only one child instance among the given models.
    #~ """
    #~ def __init__(self,get_child,**kw):
        #~ self.get_child = get_child
        #~ VirtualField.__init__(self,models.ForeignKey(**kw),self.has_child)
        
        
class HtmlBox(DisplayField):
    """
    Deserves more documentation.
    """
    pass
    
#~ class QuickAction(DisplayField):
    #~ pass
    
#~ from django.db.models.fields import Field

VIRTUAL_FIELDS = []

def resolve_virtual_fields():
    #~ global VIRTUAL_FIELDS
    for vf in VIRTUAL_FIELDS: 
        vf.lino_resolve_type()
    #~ VIRTUAL_FIELDS = None
      

class VirtualField(FakeField): # (Field):
    """
    Currently subclassed only by :class:`lino.utils.mti.EnableChild`.    
    """
    
    def __init__(self,return_type,get):
        self.return_type = return_type # a Django Field instance
        self.get = get
        VIRTUAL_FIELDS.append(self)
        """
        Normal VirtualFields are read-only and not editable.
        We don't want to require application developers to explicitly 
        specify `editable=False` in their return_type::
        
          @dd.virtualfield(dd.PriceField(_("Total")))
          def total(self,ar=None):
              return self.total_excl + self.total_vat
        """
            
    def attach_to_model(self,model,name):
        self.model = model
        self.name = name
        #~ self.return_type.name = name
        #~ self.return_type.attname = name
        #~ if issubclass(model,models.Model):
        #~ self.lino_resolve_type(model,name)
        model._meta.add_virtual_field(self)
        #~ logger.info('20120831 VirtualField %s.%s',full_model_name(model),name)
        
    def lino_resolve_type(self):
        """
        Unlike attach_to_model, this is also called on virtual 
        fields that are defined on an Actor
        """
        #~ logger.info("20120903 lino_resolve_type %s.%s", actor_or_model, name)
        #~ if self.name is not None:
            #~ if self.name != name:
                #~ raise Exception("Tried to re-use %s.%s" % (actor_or_model,name))
        #~ self.name = name
        if isinstance(self.return_type,basestring):
            self.return_type = resolve_field(self.return_type)
        if isinstance(self.return_type,models.ForeignKey):
            self.return_type.rel.to = resolve_model(self.return_type.rel.to)

        #~ removed 20120919 self.return_type.editable = self.editable
        for k in ('''to_python choices save_form_data 
          value_to_string verbose_name max_length rel
          max_digits decimal_places
          help_text
          blank'''.split()):
            setattr(self,k,getattr(self.return_type,k,None))
        #~ logger.info('20120831 VirtualField %s on %s',name,actor_or_model)
      
    def unused_contribute_to_class(self, cls, name):
        ## if defined in abstract base class, called once on each submodel
        if self.name:
            if self.name != name:
                raise Exception("Attempt to re-use %s as %s in %s" % (
                    self.__class__.__name__,name,cls))
        else:
            self.name = name
            if self.verbose_name is None and self.name:
                self.verbose_name = self.name.replace('_', ' ')
        self.model = cls
        cls._meta.add_virtual_field(self)
        #~ cls._meta.add_field(self)
        
    #~ def to_python(self,*args,**kw): return self.return_type.to_python(*args,**kw)
    #~ def save_form_data(self,*args,**kw): return self.return_type.save_form_data(*args,**kw)
    #~ def value_to_string(self,*args,**kw): return self.return_type.value_to_string(*args,**kw)
    #~ def get_choices(self): return self.return_type.choices
    #~ choices = property(get_choices)
            
    def set_value_in_object(self,request,obj,value):
        """
        Stores the specified `value` in the specified model instance `obj`.
        `request` may be `None`.
        
        Note that any implementation must also return `obj`,
        and callers must be ready to get another instance.
        This special behaviour is needed to implement 
        :class:`lino.utils.mti.EnableChild`.
        """
        raise NotImplementedError("Cannot write %r to field %s" % (value,self))
        
    #~ def value_from_object(self,request,obj):
    def value_from_object(self,obj,ar=None):
        """
        Return the value of this field in the specified model instance `obj`.
        `request` may be `None`, it's forwarded to the getter method who may 
        decide to return values depending on it.
        """
        m = self.get
        #~ assert m.func_code.co_argcount == 2, (self.name, m.func_code.co_varnames)
        #~ print self.field.name
        return m(obj,ar)
        
    def __get__(self,instance,owner):
        if instance is None: return self
        return self.value_from_object(instance)
        
    def __set__(self,instance,value):
        return self.set_value_in_object(None,instance,value)
        

def virtualfield(return_type):
    """
    Decorator to turn a method into a VirtualField.
    """
    def decorator(fn):
        return VirtualField(return_type,fn)
    return decorator
    


class Constant(object):
    """
    Deserves more documentation.
    """
    #~ get = None
    def __init__(self,text_fn):
        self.text_fn = text_fn
        
#~ def constant(verbose_name=None):
def constant():
    """
    Decorator to turn a method into a :class:`Constant`.
    """
    def decorator(fn):
        #~ def wrapped(*args):
            #~ return fn(*args)
        #~ return Constant(wrapped)
        return Constant(fn)
    return decorator


class RequestField(VirtualField):
    """
    Deserves more documentation.
    """
    def __init__(self,get,*args,**kw):
        kw.setdefault('max_length',8)
        VirtualField.__init__(self,DisplayField(*args,**kw),get)
        

def displayfield(*args,**kw):
    """
    Decorator shortcut to turn a method into a virtual DisplayField.
    """
    return virtualfield(DisplayField(*args,**kw))
    
def requestfield(*args,**kw):
    """
    Decorator to make a RequestField from a method.
    The method to decorate must return either None or a TableRequest object.
    """
    def decorator(fn):
        #~ def wrapped(*args):
            #~ return fn(*args)
        #~ return RequestField(wrapped,*args,**kw)
        return RequestField(fn,*args,**kw)
    return decorator
    
        

class MethodField(VirtualField):
    """
    Not used. See :doc:`/blog/2011/1221`.
    Similar to VirtualField, but the `get` argument to `__init__` 
    must be a string which is the name of a model method to be called 
    without a `request`.
    """
    def __init__(self,return_type,get,*args,**kw):
        self.args = args
        self.kw = kw
        VirtualField.__init__(self,return_type,get)
        
    def attach_to_model(self,model,name):
        self.get = getattr(model,get)
        VirtualField.attach_to_model(self,model,name)
      
    #~ def value_from_object(self,request,obj):
    def value_from_object(self,obj,ar=None):
        """
        Return the value of this field in the specified model instance `obj`.
        `request` is ignored.
        """
        m = self.get
        return m(obj,*self.args,**self.kw)
        

#~ class DynamicForeignKey(models.PositiveIntegerField):
    #~ """
    
    #~ """
    #~ def __init__(self, link_field, *args, **kw):
        #~ self.link_field = link_field
        #~ models.PositiveIntegerField.__init__(self,*args, **kw)
        


class unused_LinkedForeignKey(generic.GenericForeignKey):
    """
    Like a GenericForeignKey, but the content type is not stored in another model.
    Code partly copied from django.contrib.contenttypes GenericForeignKey.
    Used by :mod:`lino.modlib.links`.
    
    """
    editable = True
    verbose_name = None
    primary_key = False
    choices = None
    blank = True
    drop_zone = None
    
    
    def __init__(self,type_fk,name,*args,**kw):
        """
        type_fk is a regular ForeignKey field that points to a model whose 
        instances hold the ContentType.
        `name` is the prefix for both fields names.
        """
        self.type_fk = type_fk
        self.type_fieldname = name + '_type' 
        self.fk_field = name + '_id' 
        for k,v in kw.items():
            assert hasattr(self,k)
            setattr(self,k,v)
        
    # the following dummy methods are needed when using a DisplayField 
    # as return_type of a VirtualField
    #~ def to_python(self,*args,**kw): raise NotImplementedError
    #~ def save_form_data(self,*args,**kw): raise NotImplementedError
    #~ def value_to_string(self,*args,**kw): raise NotImplementedError
    
    def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
        """
        Handles initializing an object with the generic FK insteed of
        content-type/object-id fields.
        """
        if self.name in kwargs:
            value = kwargs.pop(self.name)
            #~ kwargs[self.ct_field] = self.get_content_type(obj=value)
            kwargs[self.fk_field] = value._get_pk_val()

    def get_content_type(self,obj):
        if not getattr(obj,self.type_fk.name+'_id'):
            #~ logger.info("20111209 get_contenttype() no type_id in %s", obj2str(obj))
            return None
        link_type = getattr(obj,self.type_fk.name)
        #~ link_type = obj.type
        return getattr(link_type,self.type_fieldname)
        


    def __get__(self, instance, instance_type=None):
        if instance is None: # accessed as a class attribute
            return self
        try:
            return getattr(instance, self.cache_attr)
        except AttributeError:
            rel_obj = None
            ct = self.get_content_type(instance)
            if ct is not None: 
                pk = getattr(instance, self.fk_field)
                if pk:
                    model = ct.model_class()
                    rel_obj = ct.get_object_for_this_type(pk=pk)
            setattr(instance, self.cache_attr, rel_obj)
            return rel_obj

    def __set__(self, instance, value):
        ct = None
        fk = None
        if value is not None:
            ct = self.get_content_type(instance) 
            fk = value._get_pk_val()

        ct = self.get_content_type(instance)
        if ct is None:
            raise ValueError("Cannot store value % because content type is undefined" % value)
        if not isinstance(value,ct.model_class()):
            raise ValueError("Expected %s instance but got %r" % (ct.model_class(),value))
        
        #~ setattr(instance, self.ct_field, ct)
        setattr(instance, self.fk_field, fk)
        setattr(instance, self.cache_attr, value)
        
    def value_from_object(self,obj):
        return self.__get__(obj)




#~ class DynamicGeneralForeignKey(models.PositiveIntegerField):
#~ class DynamicForeignKey(models.ForeignKey):
class unused_DynamicForeignKey(object):
    """
    Used to define the two fields 'a' and 'b' on the Link model.
    """
    #~ __metaclass__ = models.SubfieldBase
    
  
    #~ def __init__(self,fk_field,type_field_name,*args,**kw):
    def __init__(self,linkfield,*args,**kw):
        self.fk_field = linkfield.fk_field
        self.type_field_name = type_field_name
        models.PositiveIntegerField.__init__(self,*args,**kw)
    
    
    def to_python(self, value):
        if isinstance(value,models.Model):
            return value
        if not value:
            return value
        raise Exception("Cannot know contenttype for %r" % value)
        #~ ct = self.get_contenttype(obj)
        #~ if ct is None:
            #~ return None
        #~ return ct.get_object_for_this_type(pk=pk)
            
        #~ return value
        
    def get_prep_value(self, value):
        if value:
            return value.pk
        return None
        
    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_prep_value(value)
        #~ return self.get_db_prep_value(value,connection)
        
    #~ def save_form_data(self, instance, data):
        #~ setattr(instance, self.name, data)
        
    #~ def get_text_for_value(self,value):
        #~ return self.choicelist.get_text_for_value(value.value)
        
    def __get__(self,obj):
        return self.get_value(obj)
        
    def get_value(self,obj,request=None):
        """
        The optional 2nd argument `request` (passed from
        `VirtualField.value_from_object`) is ignored.
        """
        pk = getattr(obj,self.name+'_id')
        if pk is None:
            return None
        ct = self.get_contenttype(obj)
        if ct is None:
            return None
        return ct.get_object_for_this_type(pk=pk)
        
        #~ try:
            #~ return ct.get_object_for_this_type(pk=pk)
        #~ except model.DoesNotExist:
            #~ return None

    def set_value_in_object(self,request,obj,v):
        raise Exception("20111208")
        if not v:
            setattr(obj,self.name,None)
            return 
            
        ct = self.get_contenttype(obj)
        if ct is None:
            raise Exception("20111209")
            return None
        
        if not isinstance(v,ct.model_class()):
            raise Exception("20111209")
        setattr(obj,self.name,v.pk)


        
        

class GenericForeignKeyIdField(models.PositiveIntegerField):
    """
    Use this instead of `models.PositiveIntegerField` 
    for fields that part of a :term:`GFK` and you want 
    Lino to render them using a Combobox.
    
    Used by :class:`lino.mixins.Controllable`.
    """
    def __init__(self, type_field, *args, **kw):
        self.type_field = type_field
        models.PositiveIntegerField.__init__(self,*args, **kw)
    
class GenericForeignKey(generic.GenericForeignKey):
    """
    Add verbose_name and help_text to Django's GFK.
    Used by :class:`lino.mixins.Controllable`.
    """
    def __init__(self, ct_field="content_type", fk_field="object_id", 
          verbose_name=None,help_text=None,dont_merge=False):
        self.verbose_name = verbose_name
        self.help_text = help_text
        self.dont_merge = dont_merge
        generic.GenericForeignKey.__init__(self,ct_field,fk_field)
        
    #~ def contribute_to_class(self, cls, name):
        #~ """
        #~ @chooser(instance_values=True)
        #~ def object_id_choices(cls,object_type):
            #~ if object_type:
                #~ return object_type.model_class().objects.all()
            #~ return []
        #~ def get_object_id_display(self,value):
            #~ if self.object_type:
                #~ try:
                    #~ return unicode(self.object_type.get_object_for_this_type(pk=value))
                #~ except self.object_type.model_class().DoesNotExist,e:
                    #~ return "%s with pk %r does not exist" % (
                        #~ full_model_name(self.object_type.model_class()),value)
        #~ """
        #~ generic.GenericForeignKey.contribute_to_class(self, cls, name)
        #~ if not hasattr(cls,
        
    
#~ class FieldSet:
    #~ """
    #~ A group of fields that have a common label (`verbose_name`)
    #~ to be displayed and translated.
    #~ """
    #~ def __init__(self,verbose_name,desc=None,**child_labels):
        #~ self.verbose_name = verbose_name
        #~ self.desc = desc
        #~ self.child_labels = child_labels
        
    #~ def get_child_label(self,name):
        #~ s = self.child_labels.get(name,None)
        #~ return s
        



       
  
class QuantityField(models.CharField):
#~ class QuantityField(models.DecimalField):
#~ class QuantityField(models.Field):
    """
    A field that accepts both 
    :class:`lino.utils.quantity.Decimal`,
    :class:`lino.utils.quantity.Percentage` 
    and 
    :class:`lino.utils.quantity.Duration` 
    values.
    
    Implemented as a CharField (sorting or filter ranges may not work)
    
    QuantityFields are implemented as CharFields and 
    therefore should *not* be declared `null=True`. 
    But if `blank=True`, empty strings are converted to `None` 
    values.    
  
    """
    __metaclass__ = models.SubfieldBase
    description = _("Quantity (Decimal or Duration)")

    def __init__(self,*args,**kw):
        kw.setdefault('max_length',6)
        models.Field.__init__(self,*args,**kw)
        #~ models.CharField.__init__(self,*args,**kw)
      
    #~ def get_internal_type(self):
        #~ return "CharField"
        
    def to_python(self, value):
        if isinstance(value,Decimal):
            return value
        if value:
            return quantities.parse(value)
        return None
        
    #~ def get_db_prep_save(self, value, connection):
        #~ if value is None:
            #~ return ''
        #~ return str(value)
        
    def get_prep_value(self,value):
        if value is None:
            return ''
        return str(value)
        
class IncompleteDateField(models.CharField):
    """
    A field that behaves like a DateField, but accepts
    incomplete dates represented using 
    :class:`lino.utils.IncompleteDate`.
    """
    __metaclass__ = models.SubfieldBase

    def __init__(self,*args,**kw):
        kw.update(max_length=11)
        models.CharField.__init__(self,*args,**kw)
      
    #~ def get_internal_type(self):
        #~ return "CharField"
        
    def to_python(self, value):
        if isinstance(value,IncompleteDate):
            return value
        if isinstance(value,datetime.date):
            #~ return IncompleteDate(value.strftime("%Y-%m-%d"))
            #~ return IncompleteDate(d2iso(value))
            return IncompleteDate.from_date(value)
        if value:
            return IncompleteDate.parse(value)
        return ''
        
    def get_prep_value(self, value):
        return str(value)
        
    #~ def get_prep_value(self, value):
        #~ return '"' + str(value) + '"'
        #~ if value:
            #~ return value.format("%04d%02d%02d")
        #~ return '' 
        
    #~ def value_to_string(self, obj):
        #~ value = self._get_val_from_obj(obj)
        #~ return self.get_prep_value(value)
        
class DummyField(object):
    """
    Deserves more documentation.
    """
    def __init__(self,*args,**kw):
        pass
    def set_attributes_from_name(self,k):
        pass

class RecurrenceField(models.CharField):
    """
    Deserves more documentation.
    """
    #~ __metaclass__ = models.SubfieldBase

    def __init__(self,*args,**kw):
        kw.setdefault('max_length',200)
        models.CharField.__init__(self,*args,**kw)
      

def get_data_elem(model,name):
    #~ logger.info("20120202 get_data_elem %r,%r",model,name)
    parts = name.split('__')
    if len(parts) > 1:
        # logger.warning("20120406 RemoteField %s in %s",name,self)
        #~ model = self.model
        for n in parts:
            assert model is not None
            fld = get_data_elem(model,n)
            if fld is None:
                # raise Exception("Part %s of %s got None" % (n,model))
                raise Exception(
                    "Invalid RemoteField %s.%s (no %s in %s)" % 
                    (full_model_name(self.model),name,n,full_model_name(model)))
            if fld.rel:
                model = fld.rel.to
            else:
                model = None
        def func(obj,ar=None):
            # logger.info('20120109 %s',name)
            try:
                for n in parts:
                    obj = getattr(obj,n)
                # logger.info('20120109 %s --> %r', name,obj)
                return obj
            except Exception,e:
                return None
        return RemoteField(func,name,fld)
    
    try:
        return model._meta.get_field(name)
    except models.FieldDoesNotExist,e:
        pass
        
    #~ s = name.split('.')
    #~ if len(s) == 1:
        #~ mod = import_module(model.__module__)
        #~ rpt = getattr(mod,name,None)
    #~ elif len(s) == 2:
        #~ mod = getattr(settings.LINO.modules,s[0])
        #~ rpt = getattr(mod,s[1],None)
    #~ else:
        #~ raise Exception("Invalid data element name %r" % name)
    
    v = get_class_attr(model,name)
    if v is not None: return v
    
    for vf in model._meta.virtual_fields:
        if vf.name == name:
            return vf


def fields_list(model,field_names):
    """
    Return a list with the names of the specified fields, 
    checking whether each of them exists.
    
    Arguments: 
    `model` is any subclass of `django.db.models.Model`.
    `field_names` is a single string with a space-separated list of field names.
    
    For example if you have a model `MyModel` 
    with two fields `foo` and `bar`,
    then ``dd.fields_list(MyModel,"foo bar")`` 
    will return ``['foo','bar']``
    and ``dd.fields_list(MyModel,"foo baz")`` will raise an exception.
    """
    #~ return tuple([get_field(model,n) for n in field_names.split()])
    #~ if model.__name__ == 'Company':
        #~ print 20110929, [get_field(model,n) for n in field_names.split()]
    #~ return [get_field(model,n).name for n in field_names.split()]
    lst = []
    for name in field_names.split():
        e = get_data_elem(model,name)
        if e is None:
            raise models.FieldDoesNotExist("No data element %r in %s" % (name,model))
        lst.append(e.name)
    return lst
    #~ return [get_data_elem(model,n).name for n in field_names.split()]


def ForeignKey(othermodel,*args,**kw):
    """
    This is almost as 
    `django.db.models.ForeignKey <https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey>`, 
    except for a subtle difference: 
    it supports `othermodel` being `None` or the name of some non-installed model
    and returns a :class:`DummyField <lino.core.fields.DummyField>` 
    in that case.
    This difference is useful when designing reusable models.
    """
    if othermodel is None: 
        return DummyField(othermodel,*args,**kw)
    if isinstance(othermodel,basestring):
        if not is_installed_model_spec(othermodel):
            return DummyField(othermodel,*args,**kw)
    return models.ForeignKey(othermodel,*args,**kw)
    
