# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
from six import iteritems, string_types

import frappe
import datetime
from frappe import _
from frappe.model import default_fields, table_fields
from frappe.model.naming import set_new_name
from frappe.model.utils.link_count import notify_link_count
from frappe.model import display_fieldtypes, data_fieldtypes
from frappe.utils import (cint, flt, now, cstr, getdate, get_datetime, to_timedelta, cast_fieldtype)

max_positive_value = {
    'smallint': 2 ** 15,
    'int': 2 ** 31,
    'bigint': 2 ** 63
}

_classes = {}

doctype_python_modules = {}


def load_doctype_module(doctype, module=None, prefix="", suffix=""):
    """Returns the module object for given doctype."""
    if not module:
        module = get_doctype_module(doctype)

    app = get_module_app(module)

    key = (app, doctype, prefix, suffix)

    module_name = get_module_name(doctype, module, prefix, suffix)

    try:
        if key not in doctype_python_modules:
            doctype_python_modules[key] = frappe.get_module(module_name)
    except ImportError as e:
        raise ImportError('Module import failed for {0} ({1})'.format(doctype, module_name + ' Error: ' + str(e)))

    return doctype_python_modules[key]


def get_doctype_module(doctype):
    """Returns **Module Def** name of given doctype."""

    def make_modules_dict():
        return dict(frappe.db.sql("select name, module from tabDocType"))

    return make_modules_dict()[doctype]


def get_module_name(doctype, module, prefix="", suffix="", app=None):
    return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}{suffix}'.format( \
        app=scrub(app or get_module_app(module)),
        module=scrub(module),
        doctype=scrub(doctype),
        prefix=prefix,
        suffix=suffix)


def scrub(txt):
    return frappe.scrub(txt)


def get_module_app(module):
    try:
        return frappe.local.module_app[scrub(module)]
    except:
        return ""


def get_controller(doctype):
    pass


class BaseDocument(object):
    ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns")

    def __init__(self, d):
        self.update(d)
        self.dont_update_if_missing = []

        if hasattr(self, "__setup__"):
            self.__setup__()

    @property
    def meta(self):
        if not hasattr(self, "_meta"):
            self._meta = frappe.get_meta(self.doctype)

        return self._meta

    def update(self, d):
        if "doctype" in d:
            self.set("doctype", d.get("doctype"))

        # first set default field values of base document
        for key in default_fields:
            if key in d:
                self.set(key, d.get(key))

        for key, value in iteritems(d):
            self.set(key, value)

        return self

    def update_if_missing(self, d):
        if isinstance(d, BaseDocument):
            d = d.get_valid_dict()

        if "doctype" in d:
            self.set("doctype", d.get("doctype"))
        for key, value in iteritems(d):
            # dont_update_if_missing is a list of fieldnames, for which, you don't want to set default value
            if (self.get(key) is None) and (value is not None) and (key not in self.dont_update_if_missing):
                self.set(key, value)

    def get_db_value(self, key):
        return frappe.db.get_value(self.doctype, self.name, key)

    def get(self, key=None, filters=None, limit=None, default=None):
        if key:
            if isinstance(key, dict):
                return _filter(self.get_all_children(), key, limit=limit)
            if filters:
                if isinstance(filters, dict):
                    value = _filter(self.__dict__.get(key, []), filters, limit=limit)
                else:
                    default = filters
                    filters = None
                    value = self.__dict__.get(key, default)
            else:
                value = self.__dict__.get(key, default)

            if value is None and key not in self.ignore_in_getter \
                    and key in (d.fieldname for d in self.meta.get_table_fields()):
                self.set(key, [])
                value = self.__dict__.get(key)

            return value
        else:
            return self.__dict__

    def getone(self, key, filters=None):
        return self.get(key, filters=filters, limit=1)[0]

    def set(self, key, value, as_value=False):
        if isinstance(value, list) and not as_value:
            self.__dict__[key] = []
            self.extend(key, value)
        else:
            self.__dict__[key] = value

    def delete_key(self, key):
        if key in self.__dict__:
            del self.__dict__[key]

    def append(self, key, value=None):
        pass

    def extend(self, key, value):
        if isinstance(value, list):
            for v in value:
                self.append(key, v)
        else:
            raise ValueError

    def remove(self, doc):
        self.get(doc.parentfield).remove(doc)

    def _init_child(self, value, key):
        return dict()

    def get_valid_dict(self, sanitize=True, convert_dates_to_str=False, ignore_nulls=False):
        d = frappe._dict()
        for fieldname in self.meta.get_valid_columns():
            d[fieldname] = self.get(fieldname)

            # if no need for sanitization and value is None, continue
            if not sanitize and d[fieldname] is None:
                continue

            df = self.meta.get_field(fieldname)
            if df:
                if df.fieldtype == "Check":
                    d[fieldname] = 1 if cint(d[fieldname]) else 0

                elif df.fieldtype == "Int" and not isinstance(d[fieldname], int):
                    d[fieldname] = cint(d[fieldname])

                elif df.fieldtype in ("Currency", "Float", "Percent") and not isinstance(d[fieldname], float):
                    d[fieldname] = flt(d[fieldname])

                elif df.fieldtype in ("Datetime", "Date", "Time") and d[fieldname] == "":
                    d[fieldname] = None

                elif df.get("unique") and cstr(d[fieldname]).strip() == "":
                    # unique empty field should be set to None
                    d[fieldname] = None

                if isinstance(d[fieldname], list) and df.fieldtype not in table_fields:
                    frappe.throw(_('Value for {0} cannot be a list').format(_(df.label)))

                if convert_dates_to_str and isinstance(d[fieldname],
                                                       (datetime.datetime, datetime.time, datetime.timedelta)):
                    d[fieldname] = str(d[fieldname])

            if d[fieldname] == None and ignore_nulls:
                del d[fieldname]

        return d

    def init_valid_columns(self):
        for key in default_fields:
            if key not in self.__dict__:
                self.__dict__[key] = None

            if key in ("idx", "docstatus") and self.__dict__[key] is None:
                self.__dict__[key] = 0

        for key in self.get_valid_columns():
            if key not in self.__dict__:
                self.__dict__[key] = None

    def get_valid_columns(self):
        if self.doctype not in frappe.local.valid_columns:
            if self.doctype in ("DocField", "DocPerm") and self.parent in ("DocType", "DocField", "DocPerm"):
                from frappe.model.meta import get_table_columns
                valid = get_table_columns(self.doctype)
            else:
                valid = self.meta.get_valid_columns()

            frappe.local.valid_columns[self.doctype] = valid

        return frappe.local.valid_columns[self.doctype]

    def is_new(self):
        return self.get("__islocal")

    def as_dict(self, no_nulls=False, no_default_fields=False, convert_dates_to_str=False):
        doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str)
        doc["doctype"] = self.doctype
        for df in self.meta.get_table_fields():
            children = self.get(df.fieldname) or []
            doc[df.fieldname] = [d.as_dict(no_nulls=no_nulls) for d in children]

        if no_nulls:
            for k in list(doc):
                if doc[k] is None:
                    del doc[k]

        if no_default_fields:
            for k in list(doc):
                if k in default_fields:
                    del doc[k]

        for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers"):
            if self.get(key):
                doc[key] = self.get(key)

        return doc

    def as_json(self):
        return frappe.as_json(self.as_dict())

    def get_table_field_doctype(self, fieldname):
        return self.meta.get_field(fieldname).options

    def get_parentfield_of_doctype(self, doctype):
        fieldname = [df.fieldname for df in self.meta.get_table_fields() if df.options == doctype]
        return fieldname[0] if fieldname else None

    def db_insert(self):
        """INSERT the document (with valid columns) in the database."""
        if not self.name:
            # name will be set by document class in most cases
            set_new_name(self)

        if not self.creation:
            self.creation = self.modified = now()
            self.created_by = self.modified_by = frappe.session.user

        # if doctype is "DocType", don't insert null values as we don't know who is valid yet
        d = self.get_valid_dict(convert_dates_to_str=True,
                                ignore_nulls=self.doctype in ('DocType', 'DocField', 'DocPerm'))

        columns = list(d)
        try:
            frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns})
					VALUES ({values})""".format(
                doctype=self.doctype,
                columns=", ".join(["`" + c + "`" for c in columns]),
                values=", ".join(["%s"] * len(columns))
            ), list(d.values()))
        except Exception as e:
            if frappe.db.is_primary_key_violation(e):
                if self.meta.autoname == "hash":
                    # hash collision? try again
                    self.name = None
                    self.db_insert()
                    return

                frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name))
                raise frappe.DuplicateEntryError(self.doctype, self.name, e)

            elif frappe.db.is_unique_key_violation(e):
                # unique constraint
                self.show_unique_validation_message(e)

            else:
                raise

        self.set("__islocal", False)

    def db_update(self):
        if self.get("__islocal") or not self.name:
            self.db_insert()
            return

        d = self.get_valid_dict(convert_dates_to_str=True,
                                ignore_nulls=self.doctype in ('DocType', 'DocField', 'DocPerm'))

        # don't update name, as case might've been changed
        name = d['name']
        del d['name']

        columns = list(d)

        try:
            frappe.db.sql("""UPDATE `tab{doctype}`
				SET {values} WHERE `name`=%s""".format(
                doctype=self.doctype,
                values=", ".join(["`" + c + "`=%s" for c in columns])
            ), list(d.values()) + [name])
        except Exception as e:
            if frappe.db.is_unique_key_violation(e):
                self.show_unique_validation_message(e)
            else:
                raise

    def show_unique_validation_message(self, e):
        # TODO: Find a better way to extract fieldname
        if frappe.db.db_type != 'postgres':
            fieldname = str(e).split("'")[-2]
            label = None

            # unique_first_fieldname_second_fieldname is the constraint name
            # created using frappe.db.add_unique
            if "unique_" in fieldname:
                fieldname = fieldname.split("_", 1)[1]

            df = self.meta.get_field(fieldname)
            if df:
                label = df.label

            frappe.msgprint(_("{0} must be unique".format(label or fieldname)))

        # this is used to preserve traceback
        raise frappe.UniqueValidationError(self.doctype, self.name, e)

    def update_modified(self):
        '''Update modified timestamp'''
        self.set("modified", now())
        frappe.db.set_value(self.doctype, self.name, 'modified', self.modified, update_modified=False)

    def _fix_numeric_types(self):
        for df in self.meta.get("fields"):
            if df.fieldtype == "Check":
                self.set(df.fieldname, cint(self.get(df.fieldname)))

            elif self.get(df.fieldname) is not None:
                if df.fieldtype == "Int":
                    self.set(df.fieldname, cint(self.get(df.fieldname)))

                elif df.fieldtype in ("Float", "Currency", "Percent"):
                    self.set(df.fieldname, flt(self.get(df.fieldname)))

        if self.docstatus is not None:
            self.docstatus = cint(self.docstatus)

    def _get_missing_mandatory_fields(self):
        """Get mandatory fields that do not have any values"""

        def get_msg(df):
            if df.fieldtype in table_fields:
                return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label))

            elif self.parentfield:
                return "{}: {} {} #{}: {}: {}".format(_("Error"), frappe.bold(_(self.doctype)),
                                                      _("Row"), self.idx, _("Value missing for"), _(df.label))

            else:
                return _("Error: Value missing for {0}: {1}").format(_(df.parent), _(df.label))

        missing = []

        for df in self.meta.get("fields", {"reqd": ('=', 1)}):
            if self.get(df.fieldname) in (None, []):
                missing.append((df.fieldname, get_msg(df)))

        # check for missing parent and parenttype
        if self.meta.istable:
            for fieldname in ("parent", "parenttype"):
                if not self.get(fieldname):
                    missing.append((fieldname, get_msg(frappe._dict(label=fieldname))))

        return missing

    def get_invalid_links(self, is_submittable=False):
        '''Returns list of invalid links and also updates fetch values if not set'''

        def get_msg(df, docname):
            if self.parentfield:
                return "{} #{}: {}: {}".format(_("Row"), self.idx, _(df.label), docname)
            else:
                return "{}: {}".format(_(df.label), docname)

        invalid_links = []
        cancelled_links = []

        for df in (self.meta.get_link_fields()
                   + self.meta.get("fields", {"fieldtype": ('=', "Dynamic Link")})):
            docname = self.get(df.fieldname)

            if docname:
                if df.fieldtype == "Link":
                    doctype = df.options
                    if not doctype:
                        frappe.throw(_("Options not set for link field {0}").format(df.fieldname))
                else:
                    doctype = self.get(df.options)
                    if not doctype:
                        frappe.throw(_("{0} must be set first").format(self.meta.get_label(df.options)))

                # MySQL is case insensitive. Preserve case of the original docname in the Link Field.

                # get a map of values ot fetch along with this link query
                # that are mapped as link_fieldname.source_fieldname in Options of
                # Readonly or Data or Text type fields

                fields_to_fetch = [
                    _df for _df in self.meta.get_fields_to_fetch(df.fieldname)
                    if
                    not _df.get('fetch_if_empty')
                    or (_df.get('fetch_if_empty') and not self.get(_df.fieldname))
                ]

                if not fields_to_fetch:
                    # cache a single value type
                    values = frappe._dict(name=frappe.db.get_value(doctype, docname,
                                                                   'name', cache=True))
                else:
                    values_to_fetch = ['name'] + [_df.fetch_from.split('.')[-1]
                                                  for _df in fields_to_fetch]

                    # don't cache if fetching other values too
                    values = frappe.db.get_value(doctype, docname,
                                                 values_to_fetch, as_dict=True)

                if frappe.get_meta(doctype).issingle:
                    values.name = doctype

                if values:
                    setattr(self, df.fieldname, values.name)

                    for _df in fields_to_fetch:
                        if self.is_new() or self.docstatus != 1 or _df.allow_on_submit:
                            setattr(self, _df.fieldname, values[_df.fetch_from.split('.')[-1]])

                    notify_link_count(doctype, docname)

                    if not values.name:
                        invalid_links.append((df.fieldname, docname, get_msg(df, docname)))

                    elif (df.fieldname != "amended_from"
                          and (is_submittable or self.meta.is_submittable) and frappe.get_meta(doctype).is_submittable
                          and cint(frappe.db.get_value(doctype, docname, "docstatus")) == 2):

                        cancelled_links.append((df.fieldname, docname, get_msg(df, docname)))

        return invalid_links, cancelled_links

    def _validate_selects(self):
        if frappe.flags.in_import:
            return

        for df in self.meta.get_select_fields():
            if df.fieldname == "naming_series" or not (self.get(df.fieldname) and df.options):
                continue

            options = (df.options or "").split("\n")

            # if only empty options
            if not filter(None, options):
                continue

            # strip and set
            self.set(df.fieldname, cstr(self.get(df.fieldname)).strip())
            value = self.get(df.fieldname)

            if value not in options and not (frappe.flags.in_test and value.startswith("_T-")):
                # show an elaborate message
                prefix = _("Row #{0}:").format(self.idx) if self.get("parentfield") else ""
                label = _(self.meta.get_label(df.fieldname))
                comma_options = '", "'.join(_(each) for each in options)

                frappe.throw(_('{0} {1} cannot be "{2}". It should be one of "{3}"').format(prefix, label,
                                                                                            value, comma_options))

    def _validate_constants(self):
        if frappe.flags.in_import or self.is_new() or self.flags.ignore_validate_constants:
            return

        constants = [d.fieldname for d in self.meta.get("fields", {"set_only_once": ('=', 1)})]
        if constants:
            values = frappe.db.get_value(self.doctype, self.name, constants, as_dict=True)

        for fieldname in constants:
            df = self.meta.get_field(fieldname)

            # This conversion to string only when fieldtype is Date
            if df.fieldtype == 'Date' or df.fieldtype == 'Datetime':
                value = str(values.get(fieldname))

            else:
                value = values.get(fieldname)

            if self.get(fieldname) != value:
                frappe.throw(_("Value cannot be changed for {0}").format(self.meta.get_label(fieldname)),
                             frappe.CannotChangeConstantError)

    def _validate_length(self):
        if frappe.flags.in_install:
            return

        if self.meta.issingle:
            # single doctype value type is mediumtext
            return

        type_map = frappe.db.type_map

        for fieldname, value in iteritems(self.get_valid_dict()):
            df = self.meta.get_field(fieldname)

            if not df or df.fieldtype == 'Check':
                # skip standard fields and Check fields
                continue

            column_type = type_map[df.fieldtype][0] or None

            if column_type == 'varchar':
                default_column_max_length = type_map[df.fieldtype][1] or None
                max_length = cint(df.get("length")) or cint(default_column_max_length)

                if len(cstr(value)) > max_length:
                    self.throw_length_exceeded_error(df, max_length, value)

            elif column_type in ('int', 'bigint', 'smallint'):
                max_length = max_positive_value[column_type]

                if abs(cint(value)) > max_length:
                    self.throw_length_exceeded_error(df, max_length, value)

    def throw_length_exceeded_error(self, df, max_length, value):
        if self.parentfield and self.idx:
            reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)

        else:
            reference = "{0} {1}".format(_(self.doctype), self.name)

        frappe.throw(_("{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}") \
                     .format(reference, _(df.label), max_length, value), frappe.CharacterLengthExceededError,
                     title=_('Value too big'))

    def _validate_update_after_submit(self):
        # get the full doc with children
        db_values = frappe.get_doc(self.doctype, self.name).as_dict()

        for key in self.as_dict():
            df = self.meta.get_field(key)
            db_value = db_values.get(key)

            if df and not df.allow_on_submit and (self.get(key) or db_value):
                if df.fieldtype in table_fields:
                    # just check if the table size has changed
                    # individual fields will be checked in the loop for children
                    self_value = len(self.get(key))
                    db_value = len(db_value)

                else:
                    self_value = self.get_value(key)

                if self_value != db_value:
                    frappe.throw(_("Not allowed to change {0} after submission").format(df.label),
                                 frappe.UpdateAfterSubmitError)

    def _sanitize_content(self):
        """Sanitize HTML and Email in field values. Used to prevent XSS.

            - Ignore if 'Ignore XSS Filter' is checked or fieldtype is 'Code'
        """
        if frappe.flags.in_install:
            return

        for fieldname, value in self.get_valid_dict().items():
            if not value or not isinstance(value, string_types):
                continue

            value = frappe.as_unicode(value)

            if (u"<" not in value and u">" not in value):
                # doesn't look like html so no need
                continue

            elif "<!-- markdown -->" in value and not ("<script" in value or "javascript:" in value):
                # should be handled separately via the markdown converter function
                continue

            df = self.meta.get_field(fieldname)
            sanitized_value = value

            if df and df.get("fieldtype") in ("Data", "Code", "Small Text") and df.get("options") == "Email":
                sanitized_value = value

            elif df and (df.get("ignore_xss_filter")
                         or (df.get("fieldtype") == "Code" and df.get("options") != "Email")
                         or df.get("fieldtype") in ("Attach", "Attach Image", "Barcode")

                         # cancelled and submit but not update after submit should be ignored
                         or self.docstatus == 2
                         or (self.docstatus == 1 and not df.get("allow_on_submit"))):
                continue

            else:
                sanitized_value = value

            self.set(fieldname, sanitized_value)

    def _save_passwords(self):
        '''Save password field values in __Auth table'''
        if self.flags.ignore_save_passwords is True:
            return

        for df in self.meta.get('fields', {'fieldtype': ('=', 'Password')}):
            if self.flags.ignore_save_passwords and df.fieldname in self.flags.ignore_save_passwords: continue
            new_password = self.get(df.fieldname)
            if new_password and not self.is_dummy_password(new_password):
                # is not a dummy password like '*****'

                # set dummy password like '*****'
                self.set(df.fieldname, '*' * len(new_password))

    def get_password(self, fieldname='password', raise_exception=True):
        if self.get(fieldname) and not self.is_dummy_password(self.get(fieldname)):
            return self.get(fieldname)

        return "frappe"

    def is_dummy_password(self, pwd):
        return ''.join(set(pwd)) == '*'

    def precision(self, fieldname, parentfield=None):
        """Returns float precision for a particular field (or get global default).

        :param fieldname: Fieldname for which precision is required.
        :param parentfield: If fieldname is in child table."""
        from frappe.model.meta import get_field_precision

        if parentfield and not isinstance(parentfield, string_types):
            parentfield = parentfield.parentfield

        cache_key = parentfield or "main"

        if not hasattr(self, "_precision"):
            self._precision = frappe._dict()

        if cache_key not in self._precision:
            self._precision[cache_key] = frappe._dict()

        if fieldname not in self._precision[cache_key]:
            self._precision[cache_key][fieldname] = None

            doctype = self.meta.get_field(parentfield).options if parentfield else self.doctype
            df = frappe.get_meta(doctype).get_field(fieldname)

            if df.fieldtype in ("Currency", "Float", "Percent"):
                self._precision[cache_key][fieldname] = get_field_precision(df, self)

        return self._precision[cache_key][fieldname]

    def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False):
        from frappe.utils.formatters import format_value

        df = self.meta.get_field(fieldname)
        if not df and fieldname in default_fields:
            from frappe.model.meta import get_default_df
            df = get_default_df(fieldname)

        val = self.get(fieldname)

        if translated:
            val = _(val)

        if absolute_value and isinstance(val, (int, float)):
            val = abs(self.get(fieldname))

        if not doc:
            doc = getattr(self, "parent_doc", None) or self

        return format_value(val, df=df, doc=doc, currency=currency)

    def is_print_hide(self, fieldname, df=None, for_print=True):
        """Returns true if fieldname is to be hidden for print.

        Print Hide can be set via the Print Format Builder or in the controller as a list
        of hidden fields. Example

            class MyDoc(Document):
                def __setup__(self):
                    self.print_hide = ["field1", "field2"]

        :param fieldname: Fieldname to be checked if hidden.
        """
        meta_df = self.meta.get_field(fieldname)
        if meta_df and meta_df.get("__print_hide"):
            return True

        print_hide = 0

        if self.get(fieldname) == 0 and not self.meta.istable:
            print_hide = (df and df.print_hide_if_no_value) or (meta_df and meta_df.print_hide_if_no_value)

        if not print_hide:
            if df and df.print_hide is not None:
                print_hide = df.print_hide
            elif meta_df:
                print_hide = meta_df.print_hide

        return print_hide

    def in_format_data(self, fieldname):
        """Returns True if shown via Print Format::`format_data` property.
            Called from within standard print format."""
        doc = getattr(self, "parent_doc", self)

        if hasattr(doc, "format_data_map"):
            return fieldname in doc.format_data_map
        else:
            return True

    def reset_values_if_no_permlevel_access(self, has_access_to, high_permlevel_fields):
        """If the user does not have permissions at permlevel > 0, then reset the values to original / default"""
        to_reset = []

        for df in high_permlevel_fields:
            if df.permlevel not in has_access_to and df.fieldtype not in display_fieldtypes:
                to_reset.append(df)

        if to_reset:
            if self.is_new():
                # if new, set default value
                ref_doc = frappe.new_doc(self.doctype)
            else:
                # get values from old doc
                if self.get('parent_doc'):
                    self.parent_doc.get_latest()
                    ref_doc = [d for d in self.parent_doc.get(self.parentfield) if d.name == self.name][0]
                else:
                    ref_doc = self.get_latest()

            for df in to_reset:
                self.set(df.fieldname, ref_doc.get(df.fieldname))

    def get_value(self, fieldname):
        df = self.meta.get_field(fieldname)
        val = self.get(fieldname)

        return self.cast(val, df)

    def cast(self, value, df):
        return cast_fieldtype(df.fieldtype, value)

    def _extract_images_from_text_editor(self):
        pass


def _filter(data, filters, limit=None):
    """pass filters as:
        {"key": "val", "key": ["!=", "val"],
        "key": ["in", "val"], "key": ["not in", "val"], "key": "^val",
        "key" : True (exists), "key": False (does not exist) }"""

    out, _filters = [], {}

    if not data:
        return out

    # setup filters as tuples
    if filters:
        for f in filters:
            fval = filters[f]

            if not isinstance(fval, (tuple, list)):
                if fval is True:
                    fval = ("not None", fval)
                elif fval is False:
                    fval = ("None", fval)
                elif isinstance(fval, string_types) and fval.startswith("^"):
                    fval = ("^", fval[1:])
                else:
                    fval = ("=", fval)

            _filters[f] = fval

    for d in data:
        add = True
        for f, fval in iteritems(_filters):
            if not frappe.compare(getattr(d, f, None), fval[0], fval[1]):
                add = False
                break

        if add:
            out.append(d)
            if limit and (len(out) - 1) == limit:
                break

    return out
