# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# $Id$
#

""" Django/Gramps utilities """

#------------------------------------------------------------------------
#
# Python Modules
#
#------------------------------------------------------------------------
from __future__ import print_function

import sys
import re
import datetime
if sys.version_info[0] < 3:
    from HTMLParser import HTMLParser
else:
    from html.parser import HTMLParser

#------------------------------------------------------------------------
#
# Django Modules
#
#------------------------------------------------------------------------
from django.utils.safestring import mark_safe
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q

#------------------------------------------------------------------------
#
# Gramps-Connect Modules
#
#------------------------------------------------------------------------
import gramps.webapp.grampsdb.models as models
import gramps.webapp.grampsdb.forms as forms
from gramps.webapp import libdjango
from gramps.webapp.dbdjango import DbDjango
from gramps.gen.constfunc import cuni

#------------------------------------------------------------------------
#
# Gramps Modules
#
#------------------------------------------------------------------------
from gramps.gen.simple import SimpleTable, SimpleAccess, make_basic_stylesheet
from gramps.gen.utils.alive import probably_alive as alive
from gramps.gen.dbstate import DbState
from gramps.gen.datehandler import displayer, parser
from gramps.gen.lib.date import Date as GDate, Today
from gramps.gen.lib import Person
from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback
from gramps.gen.plug import BasePluginManager
from gramps.cli.grampscli import CLIManager
from gramps.gen.constfunc import STRTYPE
from gramps.gen.utils.grampslocale import GrampsLocale

#FIXME: A locale should be obtained from the user and used to
#initialize the locale. Passing in lang and language parameters to the
#constructor prevents querying the environment.
glocale = GrampsLocale(lang='en_US.UTF-8', languages='en')
_ = glocale.translation.gettext

TAB_HEIGHT = 200

util_filters = [
    'nbsp', 
    'date_as_text',
    'render_name',
    ]

util_tags = [
    'render',
    'media_link',
    'render_name',
    "get_person_from_handle", 
    "event_table",
    "history_table",
    "name_table",
    "surname_table",
    "citation_table",
    "note_table",
    "attribute_table",
    "data_table",
    "address_table",
    "media_table",
    "internet_table",
    "association_table",
    "location_table",
    "lds_table",
    "repository_table",
    "person_reference_table",
    "note_reference_table",
    "event_reference_table",
    "repository_reference_table",
    "citation_reference_table",
    "source_reference_table",
    "media_reference_table",
    "tag_reference_table",
    "place_reference_table",
    "children_table",
    "make_button",
    ]

#------------------------------------------------------------------------
#
# Module Constants
#
#------------------------------------------------------------------------
dji = libdjango.DjangoInterface()
dd = displayer.display
dp = parser.parse
db = DbDjango()

def register_plugins():
    dbstate = DbState()
    climanager = CLIManager(dbstate, False) # don't load db
    climanager.do_reg_plugins(dbstate, None)
    pmgr = BasePluginManager.get_instance()
    return pmgr

def get_person_from_handle(db, handle):
    # db is a Gramps Db interface
    # handle is a Person Handle
    try:
        return db.get_person_from_handle(handle)
    except:
        print("error in get_person_from_handle:", file=sys.stderr)
        import sys, traceback
        cla, exc, trbk = sys.exc_info()
        print(_("Error") + (cuni(" : %s %s") %(cla, exc)), file=sys.stderr)
        traceback.print_exc()
        return None

def probably_alive(handle):
    return False
    person = db.get_person_from_handle(handle)
    return alive(person, db)

def format_number(number, with_grouping=True):
    if number != "":
        return glocale.format("%d", number, with_grouping)
    else:
        return glocale.format("%d", 0, with_grouping)

def table_count(table, with_grouping=True):
    if table == "person":
        number = models.Person.objects.count()
    elif table == "family":
        number = models.Family.objects.count()
    elif table == "event":
        number = models.Event.objects.count()
    elif table == "note":
        number = models.Note.objects.count()
    elif table == "media":
        number = models.Media.objects.count()
    elif table == "citation":
        number = models.Citation.objects.count()
    elif table == "source":
        number = models.Source.objects.count()
    elif table == "place":
        number = models.Place.objects.count()
    elif table == "repository":
        number = models.Repository.objects.count()
    elif table == "tag":
        number = models.Tag.objects.count()
    else:
        return "[unknown table]"
    return glocale.format("%d", number, with_grouping)

def nbsp(string):
    """
    """
    if string:
        return string
    else:
        return mark_safe("&nbsp;")

class Table(object):
    """
    >>> table = Table("css_id")
    >>> table.columns("Col1", "Col2", "Col3")
    >>> table.row("1", "2", "3")
    >>> table.row("4", "5", "6")
    >>> table.get_html()
    """
    def __init__(self, id, style=None):
        self.id = id # css id
        self.db = DbDjango()
        self.access = SimpleAccess(self.db)
        self.table = SimpleTable(self.access)
        self.column_widths = None
        class Doc(object):
            def __init__(self, doc):
                self.doc = doc
                self.doc.set_link_attrs({"class": "browsecell"})
        # None is paperstyle, which is ignored:
        self.doc =  Doc(HtmlDoc(
                make_basic_stylesheet(
                    Table={"set_width":95}, 
                    TableHeaderCell={"set_bottom_border": True,
                                     "set_right_border": True,
                                     "set_padding": .1,
                                     },
                    TableDataCell={"set_bottom_border": True, 
                                   "set_right_border": True,
                                   "set_padding": .1,
                                   },
                    ), 
                None))
        self.doc.doc._backend = HtmlBackend()
        self.doc.doc.use_table_headers = True
        # You can set elements id, class, etc:
        self.doc.doc.htmllist += [
            Html('div', 
                 class_="content", 
                 id=self.id, 
                 style=("overflow: auto; height:%spx; background-color: #f4f0ec;" % TAB_HEIGHT) if not style else style)]

    def columns(self, *args):
        self.table.columns(*args)

    def row(self, *args):
        self.table.row(*list(map(nbsp, args)))

    def link(self, object_type_name, handle):
        self.table.set_link_col((object_type_name, handle))

    def links(self, links):
        """
        A list of (object_type_name, handle) pairs, one per row.
        """
        self.table.set_link_col(links)

    def get_html(self):
        retval = cuni("")
        # The HTML writer escapes data:
        self.table.write(self.doc, self.column_widths) # forces to htmllist
        # FIXME: do once, or once per table?
        self.doc.doc.build_style_declaration(self.id) # can pass id, for whole
        # FIXME: don't want to repeat this, unless diff for each table:
        retval += cuni("<style>%s</style>") % self.doc.doc.style_declaration
        # We have a couple of HTML bits that we want to unescape:
        return retval + cuni(self.doc.doc.htmllist[0]).replace("&amp;nbsp;", "&nbsp;")

def build_args(**kwargs):
    retval = cuni("")
    first = True
    for key in kwargs:
        if kwargs[key] is not "":
            if first:
                retval+= "?"
                first = False
            else:
                retval += "&"
            retval += cuni("%s=%s") % (key, kwargs[key])
    return retval

def build_search(request):
    search = request.GET.get("search", "") or request.POST.get("search", "")
    page = request.GET.get("page", "") or request.POST.get("page", "")
    return build_args(search=search, page=page)

def make_button(text, url, *args):
    newargs = []
    kwargs = cuni("")
    last = cuni("")
    for arg in args:
        if isinstance(arg, STRTYPE) and arg.startswith("?"):
            kwargs = arg
        elif isinstance(arg, STRTYPE) and arg.startswith("#"):
            last = arg
        elif arg == "":
            pass
        else:
            newargs.append(arg)
    if newargs:
        url = url % tuple(newargs)
    if text[0] in "+$-?x" or text in ["x", "^", "v", "<", "<<", ">", ">>"]:
        return mark_safe(make_image_button(text, url, kwargs, last))
    else:
        return mark_safe(cuni("""<input type="button" value="%s" onclick="document.location.href='%s%s%s'"/>""") % 
                         (text, url, kwargs, last))

def make_image_button(text, url, kwargs, last):
    if text == "x":
        button = "x"
        text = "Delete row"
    elif text == "^":
        button = "^"
        text = "Move row up"
    elif text == "v":
        button = "v"
        text = "Move row down"
    elif text.startswith("+"):
        button = "+"
        text = text[1:]
    elif text.startswith("<"):
        button = "<"
        text = text[1:]
    elif text.startswith("<<"):
        button = "<<"
        text = text[2:]
    elif text.startswith(">"):
        button = ">"
        text = text[1:]
    elif text.startswith(">>"):
        button = ">>"
        text = text[2:]
    elif text.startswith("-"):
        button = "x"
        text = text[1:]
    elif text.startswith("$"):
        button = "$"
        text = text[1:]
    elif text.startswith("?"):
        button = "?"
        text = text[1:]
    elif text.startswith("x"):
        button = "cancel"
        text = text[1:]
    return make_image_button2(button, text, url, kwargs, last)

def make_image_button2(button, text, url, kwargs="", last=""):
    if button == "cancel": 
        filename = "/images/gtk-remove.png"
    elif button == "x": # delete
        filename = "/images/gtk-remove.png"
    elif button == "^": # move up
        filename = "/images/up.png"
    elif button == "v": # move down
        filename = "/images/down.png"
    elif button == "<": # prev
        filename = "/images/previous.png"
    elif button == "<<": # start
        filename = "/images/player-start.png"
    elif button == ">": # next
        filename = "/images/next.png"
    elif button == ">>": # end
        filename = "/images/player-end.png"
    elif button == "+": # add
        filename = "/images/add.png"
    elif button == "$": # pick, share
        filename = "/images/stock_index_24.png"
    elif button == "?": # edit
        filename = "/images/text-editor.png"
    elif button == "add child to existing family":
        filename = "/images/scalable/gramps-parents-open.svg"
    elif button == "add child to new family":
        filename = "/images/scalable/gramps-parents-add.svg"
    elif button == "add spouse to existing family":
        filename = "/images/scalable/add-parent-existing-family.svg"
    elif button == "add spouse to new family":
        filename = "/images/scalable/gramps-parents.svg"
    return cuni("""<img height="22" width="22" alt="%s" title="%s" src="%s" onmouseover="buttonOver(this)" onmouseout="buttonOut(this)" onclick="document.location.href='%s%s%s'" style="background-color: lightgray; border: 1px solid lightgray; border-radius:5px; margin: 0px 1px; padding: 1px;" />""") % (text, text, filename, url, kwargs, last)

def event_table(obj, user, act, url, args):
    retval = cuni("")
    has_data = False
    cssid = "tab-events"
    table = Table("event_table")
    table.columns(
        "",
        _("Description"), 
        _("Type"),
        _("ID"),
        _("Date"),
        _("Place"),
        _("Role"))
    table.column_widths = [11, 19, 10, 7, 20, 23, 10]
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        event_ref_list = models.EventRef.objects.filter(
            object_id=obj.id, 
            object_type=obj_type).order_by("order")
        event_list = [(o.ref_object, o) for o in event_ref_list]
        links = []
        count = 1
        for (djevent, event_ref) in event_list:
            table.row(Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser and act == "view" else "",
                djevent.description,
                table.db.get_event_from_handle(djevent.handle),
                djevent.gramps_id, 
                display_date(djevent),
                get_title(djevent.place),
                cuni(event_ref.role_type))
            links.append(('URL', event_ref.get_url()))
            has_data = True
            count += 1
        table.links(links)
    retval += cuni("""<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
    if user.is_superuser and act == "view":
        retval += make_button(_("+Add New Event"), (url % args).replace("$act", "add"))
        retval += make_button(_("$Add Existing Event"), (url % args).replace("$act", "share"))
    else:
        retval += cuni("""<div style="height: 26px;"></div>""") # to keep tabs same height
    retval += cuni("""</div>""")
    retval += table.get_html()
    if user.is_superuser and act == "view":
        count = 1
        retval = retval.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        retval = retval.replace("}}", """</div>""")
        for (djevent, event_ref) in event_list:
            item = obj.__class__.__name__.lower()
            retval = retval.replace("[[x%d]]" % count, make_button("x", cuni("/%s/%s/remove/eventref/%d") % (item, obj.handle, count)))
            retval = retval.replace("[[^%d]]" % count, make_button("^", cuni("/%s/%s/up/eventref/%d") % (item, obj.handle, count)))
            retval = retval.replace("[[v%d]]" % count, make_button("v", cuni("/%s/%s/down/eventref/%d") % (item, obj.handle, count)))
            count += 1
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def history_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-history"
    table = Table("history_table")
    table.columns(
        _("Action"), 
        _("Comment"),
        )
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        for entry in models.Log.objects.filter(
            object_id=obj.id, 
            object_type=obj_type):
            table.row(
                cuni("%s on %s by %s") % (entry.log_type, 
                                    entry.last_changed,
                                    entry.last_changed_by),
                entry.reason)
            has_data = True
        table.row(
            cuni("Latest on %s by %s") % (obj.last_changed,
                                     obj.last_changed_by),
            "Current status")
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def name_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-names"
    table = Table("name_table")
    table.columns(_("Name"), 
                  _("Type"),
                  _("Group As"),
                  _("Source"),
                  _("Note Preview"))
    if user.is_authenticated() or obj.public:
        links = []
        for name in obj.name_set.all().order_by("order"):
            obj_type = ContentType.objects.get_for_model(name)
            citationq = dji.CitationRef.filter(object_type=obj_type,
                                               object_id=name.id).count() > 0
            note_refs = dji.NoteRef.filter(object_type=obj_type,
                                           object_id=name.id)
            note = cuni("")
            if note_refs.count() > 0:
                try:
                    note = dji.Note.get(id=note_refs[0].object_id).text[:50]
                except:
                    note = None
            table.row(make_name(name, user),
                      cuni(name.name_type) + ["", " (preferred)"][int(name.preferred)],
                      name.group_as,
                      ["No", "Yes"][citationq],
                      note)
            links.append(('URL', 
                          # url is "/person/%s/name"
                          (url % name.person.handle) + (cuni("/%s") % name.order)))
            has_data = True
        table.links(links)
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Name"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def surname_table(obj, user, act, url=None, *args):
    person_handle = args[0]
    order = args[1]
    retval = cuni("")
    has_data = False
    cssid = "tab-surnames"
    table = Table("surname_table")
    table.columns(_("Order"), _("Surname"),)
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Surname"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    if user.is_authenticated() or obj.public:
        try:
            name = obj.name_set.filter(order=order)[0]
        except:
            name = None
        if name:
            for surname in name.surname_set.all().order_by("order"):
                table.row(cuni(surname.order), surname)
                has_data = True
            retval += table.get_html()
        else:
            retval += cuni("<p id='error'>No such name order = %s</p>") % order
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def citation_table(obj, user, act, url=None, *args):
    # FIXME: how can citation_table and source_table both be on same
    # page? This causes problems with form names, tab names, etc.
    retval = cuni("")
    has_data = False
    cssid = "tab-sources"
    table = Table("citation_table")
    table.columns("", 
                  _("ID"), 
                  _("Confidence"),
                  _("Page"))
    table.column_widths = [11, 10, 49, 30]
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        citation_refs = dji.CitationRef.filter(object_type=obj_type,
                                               object_id=obj.id).order_by("order")
        links = []
        count = 1
        for citation_ref in citation_refs:
            if citation_ref.citation:
                citation = table.db.get_citation_from_handle(
                    citation_ref.citation.handle)
                table.row(Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser and url and act == "view" else "",
                          citation.gramps_id,
                          cuni(citation.confidence),
                          cuni(citation.page),
                          )
                links.append(('URL', citation_ref.get_url()))
                has_data = True
                count += 1
        table.links(links)
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add New Citation"), (url % args).replace("$act", "add"))
        retval += make_button(_("$Add Existing Citation"), (url % args).replace("$act", "share"))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if user.is_superuser and url and act == "view":
        retval = retval.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        retval = retval.replace("}}", """</div>""")
        count = 1
        for citation_ref in citation_refs:
            item = obj.__class__.__name__.lower()
            retval = retval.replace("[[x%d]]" % count, make_button("x", cuni("/%s/%s/remove/citationref/%d") % (item, obj.handle, count)))
            retval = retval.replace("[[^%d]]" % count, make_button("^", cuni("/%s/%s/up/citationref/%d") % (item, obj.handle, count)))
            retval = retval.replace("[[v%d]]" % count, make_button("v", cuni("/%s/%s/down/citationref/%d") % (item, obj.handle, count)))
            count += 1
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def repository_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-repositories"
    table = Table("repository_table")
    table.columns(
        "",
        _("ID"),
        _("Title"),
        _("Call number"),
        _("Type"),
        )
    table.column_widths = [11, 49, 20, 20]
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add New Repository"), (url % args).replace("$act", "add"))
        retval += make_button(_("$Add Existing Repository"), (url % args).replace("$act", "share"))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        refs = dji.RepositoryRef.filter(object_type=obj_type,
                                        object_id=obj.id)
        count = 1
        for repo_ref in refs:
            repository = repo_ref.ref_object
            table.row(
                Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser else "",
                repository.gramps_id,
                repository.name,
                repo_ref.call_number, 
                cuni(repository.repository_type),
                )
            has_data = True
            count += 1
        text = table.get_html()
        text = text.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        text = text.replace("}}", """</div>""")
        count = 1
        for repo_ref in refs:
            item = obj.__class__.__name__.lower()
            text = text.replace("[[x%d]]" % count, make_button("x", cuni("/%s/%s/remove/repositoryref/%d") % (item, obj.handle, count)))
            text = text.replace("[[^%d]]" % count, make_button("^", cuni("/%s/%s/up/repositoryref/%d") % (item, obj.handle, count)))
            text = text.replace("[[v%d]]" % count, make_button("v", cuni("/%s/%s/down/repositoryref/%d") % (item, obj.handle, count)))
            count += 1
        retval += text
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def note_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-notes"
    table = Table("note_table")
    table.columns(
        "",
        _("ID"),
        _("Type"),
        _("Note"))
    table.column_widths = [11, 10, 20, 59]
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        note_refs = dji.NoteRef.filter(object_type=obj_type,
                                       object_id=obj.id).order_by("order")
        links = []
        count = 1
        for note_ref in note_refs:
            note = note_ref.ref_object
            table.row(Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser else "",
                      note.gramps_id,
                      cuni(note.note_type),
                      note.text[:50]
                      )
            links.append(('URL', note_ref.get_url()))
            has_data = True
            count += 1
        table.links(links)
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add New Note"), (url % args).replace("$act", "add"))
        retval += make_button(_("$Add Existing Note"), (url % args).replace("$act", "share"))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    text = table.get_html()
    text = text.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
    text = text.replace("}}", """</div>""")
    if user.is_authenticated() or obj.public:
        count = 1
        for note_ref in note_refs:
            item = obj.__class__.__name__.lower()
            text = text.replace("[[x%d]]" % count, make_button("x", cuni("/%s/%s/remove/noteref/%d") % (item, obj.handle, count)))
            text = text.replace("[[^%d]]" % count, make_button("^", cuni("/%s/%s/up/noteref/%d") % (item, obj.handle, count)))
            text = text.replace("[[v%d]]" % count, make_button("v", cuni("/%s/%s/down/noteref/%d") % (item, obj.handle, count)))
            count += 1
    retval += text
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

def data_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-data"
    table = Table("data_table")
    table.columns(
        "",
        _("Type"), 
        _("Value"),
        )
    table.column_widths = [11, 39, 50]
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        # /data/$act/citation/%s
        retval += make_button(_("+Add Data"), (url.replace("$act", "add") % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    if user.is_authenticated() or obj.public:
        item_class = obj.__class__.__name__.lower()
        if item_class == "citation":
            refs = models.CitationAttribute.objects.filter(citation=obj).order_by("order")
        elif item_class == "source":
            refs = models.SourceAttribute.objects.filter(source=obj).order_by("order")
        count = 1
        for ref in refs:
            if item_class == "citation":
                ref_obj = ref.citation
            elif item_class == "source":
                ref_obj = ref.source
            table.row(
                Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser else "",
                ref_obj.key,
                ref_obj.value,
                )
            has_data = True
            count += 1
        text = table.get_html()
        text = text.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        text = text.replace("}}", """</div>""")
        count = 1
        for repo_ref in refs:
            text = text.replace("[[x%d]]" % count, make_button("x", cuni("/%s/%s/remove/attribute/%d") % (item_class, obj.handle, count)))
            text = text.replace("[[^%d]]" % count, make_button("^", cuni("/%s/%s/up/attribute/%d") % (item_class, obj.handle, count)))
            text = text.replace("[[v%d]]" % count, make_button("v", cuni("/%s/%s/down/attribute/%d") % (item_class, obj.handle, count)))
            count += 1
        retval += text
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def attribute_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-attributes"
    table = Table("attribute_table")
    table.columns(_("Type"), 
                  _("Value"),
                  )
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        attributes = dji.Attribute.filter(object_type=obj_type,
                                          object_id=obj.id)
        for attribute in attributes:
            table.row(attribute.attribute_type.name,
                      attribute.value)
            has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Attribute"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def address_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-addresses"
    table = Table("address_table")
    table.columns(_("Date"), 
                  _("Address"),
                  _("City"),
                  _("State"),
                  _("Country"))
    if user.is_authenticated() or obj.public:
        for address in obj.address_set.all().order_by("order"):
            locations = address.location_set.all().order_by("order")
            for location in locations:
                table.row(display_date(address),
                          location.street,
                          location.city,
                          location.state,
                          location.country)
                has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Address"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def media_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-media"
    table = Table("media_table")
    table.columns(_("Description"), 
                  _("Type"),
                  _("Path/Filename"),
                  )
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        media_refs = dji.MediaRef.filter(object_type=obj_type,
                                        object_id=obj.id)
        for media_ref in media_refs:
            media = table.db.get_object_from_handle(
                media_ref.ref_object.handle)
            table.row(table.db.get_object_from_handle(media.handle),
                      cuni(media_ref.ref_object.desc),
                      media_ref.ref_object.path)
            has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add New Media"), (url % args).replace("$act", "add"))
        retval += make_button(_("$Add Existing Media"), (url % args).replace("$act", "share"))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def internet_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-internet"
    table = Table("internet_table")
    table.columns(_("Type"),
                  _("Path"),
                  _("Description"))
    if user.is_authenticated() or obj.public:
        urls = dji.Url.filter(person=obj)
        for url_obj in urls:
            table.row(cuni(url_obj.url_type),
                      url_obj.path,
                      url_obj.desc)
            has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Internet"), (cuni(url) % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def association_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-associations"
    table = Table("association_table")
    table.columns(_("Name"), 
                  _("ID"),
                  _("Association"))
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add Association"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    if user.is_authenticated() or obj.public:
        person = table.db.get_person_from_handle(obj.handle)
        if person:
            links = []
            count = 1
            associations = person.get_person_ref_list()
            for association in associations: # PersonRef
                table.row(Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser and url and act == "view" else "",
                          association.ref_object.get_primary_name(), 
                          association.ref_object.gramps_id, 
                          association.description, 
                          )
                links.append(('URL', "/person/%s/association/%d" % (obj.handle, count))) 
                has_data = True
                count += 1
            table.links(links)
            text = table.get_html()
            text = text.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
            text = text.replace("}}", """</div>""")
            count = 1
            for association in associations: # PersonRef
                text = text.replace("[[x%d]]" % count, make_button("x", "/person/%s/remove/association/%d" % (obj.handle, count)))
                text = text.replace("[[^%d]]" % count, make_button("^", "/person/%s/up/association/%d" % (obj.handle, count)))
                text = text.replace("[[v%d]]" % count, make_button("v", "/person/%s/down/association/%d" % (obj.handle, count)))
            retval += text
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def location_table(obj, user, act, url=None, *args):	 
    # obj is Place or Address
    retval = cuni("")
    has_data = False	 
    cssid = "tab-alternatelocations"	 
    table = Table("location_table")	 
    table.columns(_("Street"),	 
                  _("Locality"),	 
                  _("City"),	 
                  _("State"),	 
                  _("Country"))	 
    if user.is_authenticated() or obj.public:
        # FIXME: location confusion!
        # The single Location on the Location Tab is here too?
        # I think if Parish is None, then these are single Locations;
        # else they are in the table of alternate locations
        for location in obj.location_set.all().order_by("order"):
            table.row(
                location.street,
                location.locality,
                location.city,
                location.state,
                location.country)
            has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":	 
        retval += make_button(_("+Add Address"), (url % args))	 
    else:	 
        retval += nbsp("") # to keep tabs same height	 
    retval += """</div>"""
    retval += table.get_html()	 
    if has_data:	 
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid	 
    return retval	 

def lds_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-lds"
    table = Table("lds_table")
    table.columns(_("Type"), 
                  _("Date"),
                  _("Status"),
                  _("Temple"),
                  _("Place"))
    if user.is_authenticated() or obj.public:
        obj_type = ContentType.objects.get_for_model(obj)
        ldss = obj.lds_set.all().order_by("order")
        for lds in ldss:
            table.row(cuni(lds.lds_type),
                      display_date(lds),
                      cuni(lds.status),
                      lds.temple,
                      get_title(lds.place))
            has_data = True
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        retval += make_button(_("+Add LDS"), (url % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += table.get_html()
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval

def person_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    text1 = cuni("")
    text2 = cuni("")
    table1 = Table("person_reference_table", style="background-color: #f4f0ec;")
    table1.columns(
        "As Spouse",
        _("ID"),
        _("Reference"), 
        )
    table1.column_widths = [11, 10, 79]
    table2 = Table("person_reference_table", style="background-color: #f4f0ec;")
    table2.columns(
        "As Child",
        _("ID"),
        _("Reference"), 
        )
    table2.column_widths = [11, 10, 79]
    if (user.is_authenticated() or obj.public) and act != "add":
        count = 1
        for through in models.MyFamilies.objects.filter(person=obj).order_by("order"):
            reference = through.family
            table1.row(
                Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser else "",
                reference.gramps_id,
                reference,
                )
            has_data = True
            count += 1
        text1 += table1.get_html()
        text1 = text1.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        text1 = text1.replace("}}", """</div>""")
        count = 1
        for through in models.MyFamilies.objects.filter(person=obj).order_by("order"):
            reference = through.family
            text1 = text1.replace("[[x%d]]" % count, make_button("x", "/person/%s/remove/family/%d" % (obj.handle, count)))
            text1 = text1.replace("[[^%d]]" % count, make_button("^", "/person/%s/up/family/%d" % (obj.handle, count)))
            text1 = text1.replace("[[v%d]]" % count, make_button("v", "/person/%s/down/family/%d" % (obj.handle, count)))
            count += 1
        # Parent Families
        count = 1
        for through in models.MyParentFamilies.objects.filter(person=obj).order_by("order"):
            reference = through.family
            table2.row(
                Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser else "",
                reference.gramps_id,
                reference,
                )
            has_data = True
            count += 1
        text2 += table2.get_html()
        text2 = text2.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        text2 = text2.replace("}}", """</div>""")
        count = 1
        for through in models.MyParentFamilies.objects.filter(person=obj).order_by("order"):
            reference = through.family
            text2 = text2.replace("[[x%d]]" % count, make_button("x", "/person/%s/remove/parentfamily/%d" % (obj.handle, count)))
            text2 = text2.replace("[[^%d]]" % count, make_button("^", "/person/%s/up/parentfamily/%d" % (obj.handle, count)))
            text2 = text2.replace("[[v%d]]" % count, make_button("v", "/person/%s/down/parentfamily/%d" % (obj.handle, count)))
            count += 1

    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    retval += make_image_button2("add spouse to new family", 
                                 _("Add as Spouse to New Family"), 
                                 "/family/add/spouse/%s" % obj.handle)
    retval += make_image_button2("add spouse to existing family", 
                                 _("Add as Spouse to Existing Family"), 
                                 "/family/share/spouse/%s" % obj.handle)
    retval += "&nbsp;"
    retval += make_image_button2("add child to new family", 
                                 _("Add as Child to New Family"), 
                                 "/family/add/child/%s" % obj.handle)
    retval += make_image_button2("add child to existing family", 
                                 _("Add as Child to Existing Family"), 
                                 "/family/share/child/%s" % obj.handle)
    retval += """</div>"""
    retval += """<div style="overflow: auto; height:%spx;">""" % TAB_HEIGHT
    retval += text1 + text2 + "</div>"
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def note_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("note_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated()  or obj.public) and act != "add":
        for reference in models.NoteRef.objects.filter(ref_object=obj):
            ref_from_class = reference.object_type.model_class()
            item = ref_from_class.objects.get(id=reference.object_id)
            table.row(
                item.__class__.__name__,
                item,
                item.gramps_id)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += """ <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""" % cssid
    return retval 

def event_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("event_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated() or obj.public) and act != "add":
        for reference in models.EventRef.objects.filter(ref_object=obj):
            ref_from_class = reference.object_type.model_class()
            try:
                item = ref_from_class.objects.get(id=reference.object_id)
            except:
                print("Warning: Corrupt reference: %s" % reference)
                continue
            table.row(
                item.__class__.__name__,
                item,
                item.gramps_id)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def repository_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("repository_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated() or obj.public) and act != "add":
        for reference in models.RepositoryRef.objects.filter(ref_object=obj):
            ref_from_class = reference.object_type.model_class()
            item = ref_from_class.objects.get(id=reference.object_id)
            table.row(
                item.__class__.__name__,
                item,
                item.gramps_id)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def citation_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("citation_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
#        _("ID")
        )
    if (user.is_authenticated() or obj.public) and act != "add":
        for reference in models.CitationRef.objects.filter(citation=obj):
            ref_from_class = reference.object_type.model_class()
            item = ref_from_class.objects.get(id=reference.object_id)
            table.row(
                item.__class__.__name__,
                item)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def source_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("source_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated() or obj.public) and act != "add":
        for item in obj.citation_set.all():
            table.row(
                item.__class__.__name__,
                item,
                item.gramps_id)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def media_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("media_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated() or obj.public) and act != "add":
        for reference in models.MediaRef.objects.filter(ref_object=obj):
            ref_from_class = reference.object_type.model_class()
            item = ref_from_class.objects.get(id=reference.object_id)
            table.row(
                item.__class__.__name__,
                item,
                item.gramps_id)
            has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def place_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("place_reference_table")
    table.columns(
        _("Type"),
        _("Reference"))
    if (user.is_authenticated() or obj.public) and act != "add":
        # location, url, event, lds
        querysets = [obj.location_set, obj.url_set, obj.event_set, obj.lds_set]
        for queryset in querysets:
            for item in queryset.all():
                table.row(
                    item.__class__.__name__,
                    item)
                has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

def tag_reference_table(obj, user, act):
    retval = cuni("")
    has_data = False
    cssid = "tab-references"
    table = Table("tag_reference_table")
    table.columns(
        _("Type"),
        _("Reference"), 
        _("ID"))
    if (user.is_authenticated() or obj.public) and act != "add":
        querysets = [obj.person_set, obj.family_set, obj.note_set, obj.media_set]
        for queryset in querysets:
            for item in queryset.all():
                table.row(
                    item.__class__.__name__,
                    item,
                    item.gramps_id)
                has_data = True
    retval += table.get_html()
    retval += nbsp("") # to keep tabs same height
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval 

class Link(object):
    def __init__(self, string, url=None):
        self.string = string
        self.url = url
    def get_url(self):
        return self.url
    def __str__(self):
        return self.string

def children_table(obj, user, act, url=None, *args):
    retval = cuni("")
    has_data = False
    cssid = "tab-children"
    table = Table("children_table")
    table.columns(
        "",
        _("#"),
        _("ID"),
        _("Name"),
        _("Gender"),
        _("Paternal"),
        _("Maternal"),
        _("Birth Date"),
        )
    table.column_widths = [11, 5, 10, 29, 8, 8, 10, 19] 

    family = obj
    obj_type = ContentType.objects.get_for_model(family)
    childrefs = dji.ChildRef.filter(object_id=family.id,
                                    object_type=obj_type).order_by("order")
    links = []
    count = 1
    for childref in childrefs:
        child = childref.ref_object
        if user.is_authenticated() or obj.public:
            table.row(Link("{{[[x%d]][[^%d]][[v%d]]}}" % (count, count, count)) if user.is_superuser and url and act == "view" else "",
                      cuni(count), 
                      cuni("[%s]") % child.gramps_id,
                      render_name(child, user),
                      child.gender_type,
                      childref.father_rel_type,
                      childref.mother_rel_type,
                      date_as_text(child.birth, user) if child.birth else "",
                      )
            has_data = True
            links.append(('URL', childref.get_url()))
            count += 1
        else:
            table.row("",
                      cuni(count), 
                      cuni("[%s]") % child.gramps_id,
                      render_name(child, user) if not child.private else "[Private]",
                      child.gender_type if not child.private else "[Private]",
                      "[Private]",
                      "[Private]",
                      "[Private]",
                      )
            if not child.private and not childref.private:
                links.append(('URL', childref.get_url()))
            else:
                links.append((None, None))
            has_data = True
            count += 1
    table.links(links)
    text = table.get_html()
    retval += """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">"""
    if user.is_superuser and url and act == "view":
        text = text.replace("{{", """<div style="background-color: lightgray; padding: 2px 0px 0px 2px">""")
        text = text.replace("}}", """</div>""")
        count = 1
        for childref in childrefs:
            text = text.replace("[[x%d]]" % count, make_button("x", cuni("/family/%s/remove/child/%d") % (family.handle, count)))
            text = text.replace("[[^%d]]" % count, make_button("^", cuni("/family/%s/up/child/%d") % (family.handle, count)))
            text = text.replace("[[v%d]]" % count, make_button("v", cuni("/family/%s/down/child/%d") % (family.handle, count)))
            count += 1
        retval += make_button(_("+Add New Person as Child"), (url.replace("$act", "add") % args))
        retval += make_button(_("$Add Existing Person as Child"), (url.replace("$act", "share") % args))
    else:
        retval += nbsp("") # to keep tabs same height
    retval += """</div>"""
    retval += text
    if has_data:
        retval += cuni(""" <SCRIPT LANGUAGE="JavaScript">setHasData("%s", 1)</SCRIPT>\n""") % cssid
    return retval

## FIXME: these dji function wrappers just use the functions
## written for the import/export. Can be done much more directly.

def get_title(place):
    if place:
        return place.title
    else:
        return ""

def display_date(obj):
    date_tuple = dji.get_date(obj)
    if date_tuple:
        gdate = GDate()
        gdate.unserialize(date_tuple)
        return dd(gdate)
    else:
        return ""

def media_link(handle, user, act):
    retval = cuni("""<a href="%s"><img src="%s" /></a>""") % (
        cuni("/media/%s/full") % handle, 
        cuni("/media/%s/thumbnail") % handle)
    return retval

def render(formfield, user, act, id=None, url=None, *args):
    if not user.is_authenticated():
        act = "view"
    if act == "view": # show as text
        fieldname = formfield.name # 'surname'
        try:
            item = getattr(formfield.form.model, fieldname)
            if (item.__class__.__name__ == 'ManyRelatedManager'):
                retval = cuni(", ").join([i.get_link() for i in item.all()])
            else:
                if url:
                    retval = cuni("""<a href="%s">%s</a>""") % (url % args, item)
                elif hasattr(item, "get_link"):
                    retval = item.get_link()
                else:
                    retval = cuni(item)
                #### Some cleanup:
                if fieldname == "private": # obj.private
                    if retval == "True":
                        retval = "Private"
                    elif retval == "False":
                        retval = "Not private"
                else:
                    if retval == "True":
                        retval = "Yes"
                    elif retval == "False":
                        retval = "No"
        except:
            # name, "prefix"
            try:
                retval = cuni(formfield.form.data[fieldname]) 
            except:
                retval = "[None]"
    else: # show as widget
        if id != None:
            retval = formfield.as_widget(attrs={"id": id})
        else:
            retval = formfield.as_widget()
        if formfield.name == "private":
            retval += " Private"
    return retval

def render_name(name, user, act=None):
    """
    Given a Django or Gramps object, render the name and return.  This
    function uses authentication, privacy and probably_alive settings.
    """
    if name is None:
        return "[None]"
    elif isinstance(name, models.Name):
        if not user.is_authenticated():
            name.sanitize()
        try:
            surname = name.surname_set.get(primary=True)
        except:
            surname = "[No primary surname]"
        return cuni("%s, %s") % (surname, name.first_name)
    elif isinstance(name, forms.NameForm):
        if not user.is_authenticated():
            name.model.sanitize()
        try:
            surname = name.model.surname_set.get(primary=True)
        except:
            surname = "[No primary surname]"
        return cuni("%s, %s") % (surname, 
                           name.model.first_name)
    elif isinstance(name, Person): # name is a Person
        person = name
        try:
            name = person.get_primary_name()
        except:
            name = None
        if name is None:
            return "[No preferred name]"
        if not user.is_authenticated():
            name.sanitize()
        try:
            surname = name.surname_set.get(primary=True)
        except:
            surname = "[No primary surname]"
        return cuni("%s, %s") % (surname, name.first_name)
    elif isinstance(name, models.Person): # django person
        person = name
        try:
            name = person.name_set.get(preferred=True)
        except:
            return "Error"
        return render_name(name, user)
    else: # no name object
        return "[No preferred name]"

def make_name(name, user):
    return render_name(name, user)

def date_as_text(obj, user):
    """
    Given a Django object, render the date as text and return.  This
    function uses authentication settings.
    """
    if user.is_authenticated() or (obj and obj.public):
        if obj:
            date_tuple = dji.get_date(obj)
            if date_tuple:
                gdate = GDate().unserialize(date_tuple)
                return dd(gdate)
        return ""
    else:
        return ""

def person_get_event(person, event_type=None):
    event_ref_list = dji.get_event_ref_list(person)
    if event_type:
        index = libdjango.lookup_role_index(event_type, event_ref_list)
        if index >= 0:
            event_handle = event_ref_list[index][3]
            # (False, [], [], cuni('b2cfa6cdec87392cf3b'), (1, cuni('Primary')))
            # WARNING: the same object can be referred to more than once
            objs = models.EventRef.objects.filter(ref_object__handle=event_handle)
            if objs.count() > 0:
                return display_date(objs[0].ref_object)
            else:
                return ""
        else:
            return ""
    else:
        retval = [[obj.ref_object for obj in 
                   models.EventRef.objects.filter(ref_object__handle=event_handle[3])] 
                  for event_handle in event_ref_list]
        return [j for i in retval for j in i]

def boolean(s):
    return s.lower() in ["true", "1", "yes", "y", "t"]

def update_last_changed(obj, user):
    obj.last_changed = datetime.datetime.now()
    obj.last_changed_by = user

register_plugins()

# works after registering plugins:
from gramps.plugins.docgen.htmldoc import HtmlDoc 
from gramps.plugins.lib.libhtmlbackend import HtmlBackend, DocBackend, process_spaces
from gramps.plugins.lib.libhtml import Html

class WebAppBackend(HtmlBackend):
    SUPPORTED_MARKUP = [
            DocBackend.BOLD,
            DocBackend.ITALIC,
            DocBackend.UNDERLINE,
            DocBackend.FONTFACE,
            DocBackend.FONTSIZE,
            DocBackend.FONTCOLOR,
            DocBackend.HIGHLIGHT,
            DocBackend.SUPERSCRIPT,
            DocBackend.LINK,
            ]

    STYLETAG_MARKUP = {
        DocBackend.BOLD        : ("<b>", "</b>"),
        DocBackend.ITALIC      : ("<i>", "</i>"),
        DocBackend.UNDERLINE   : ('<u>', '</u>'),
        DocBackend.SUPERSCRIPT : ("<sup>", "</sup>"),
    }

### Taken from Narrated Web Report
class StyledNoteFormatter(object):
    def __init__(self, database):
        self.database = database
        self._backend = WebAppBackend()
        self._backend.build_link = self.build_link

    def format(self, note):
        return self.styled_note(note.get_styledtext())

    def styled_note(self, styledtext):
        text = cuni(styledtext)
        if not text:
            return ''
        s_tags = styledtext.get_tags()
        markuptext = self._backend.add_markup_from_styled(text, s_tags, split='\n').replace("\n\n", "<p></p>").replace("\n", "<br/>")
        return markuptext

    def build_link(self, prop, handle, obj_class):
        """
        Build a link to an item.
        """
        if prop == "gramps_id":
            if obj_class in self.database.get_table_names():
                obj = self.database.get_table_metadata(obj_class)["gramps_id_func"](handle)
                if obj:
                    handle = obj.handle
                else:
                    raise AttributeError(cuni("gramps_id '%s' not found in '%s'") % 
                                         (handle, obj_class))
            else:
                raise AttributeError(cuni("invalid gramps_id lookup ") + 
                                     cuni("in table name '%s'") % obj_class)
        # handle, ppl
        return cuni("/%s/%s") % (obj_class.lower(), handle)

class WebAppParser(HTMLParser):
    BOLD = 0
    ITALIC = 1
    UNDERLINE = 2
    FONTFACE = 3
    FONTSIZE = 4
    FONTCOLOR = 5
    HIGHLIGHT = 6 # background color
    SUPERSCRIPT = 7
    LINK = 8

    def __init__(self):
        HTMLParser.__init__(self)
        self.__text = ""
        self.__tags = {}
        self.__stack = []

    def handle_data(self, data):
        self.__text += data

    def push(self, pos, tag, attrs):
        self.__stack.append([pos, tag, attrs])

    def pop(self):
        return self.__stack.pop()

    def handle_starttag(self, tag, attrs):
        if tag == "br":
            self.__text += "\n"
            return
        self.push(len(self.__text), tag.lower(), attrs)

    def handle_startstoptag(self, tag, attrs):
        if tag == "br":
            self.__text += "\n"
            return
        elif tag == "p":
            self.__text += "\n\n"
            return
        else:
            print("Unhandled start/stop tag '%s'" % tag)

    def handle_endtag(self, tag):
        tag = tag.lower()
        if tag in ["br"]: return
        (start_pos, start_tag, attrs) = self.pop()
        attrs = dict(attrs)
        if tag != start_tag: return # skip <i><b></i></b> formats
        arg = None
        tagtype = None
        if tag == "span":
            # "span": get color, font, size
            if "style" in attrs:
                style = attrs["style"]
                if 'color' in style:
                    tagtype = self.FONTCOLOR
                    match = re.match("color:([^;]*);", style)
                    if match:
                        arg = match.groups()[0]
                    else:
                        print("Unhandled color tag: '%s'" % style)
                elif 'background-color' in style:
                    tagtype = self.HIGHLIGHT
                    match = re.match("background-color:([^;]*);", style)
                    if match:
                        arg = match.groups()[0]
                    else:
                        print("Unhandled background-color tag: '%s'" % style)
                elif "font-family" in style:
                    tagtype = self.FONTFACE
                    match = re.match("font-family:'([^;]*)';", style)
                    if match:
                        arg = match.groups()[0]
                    else:
                        print("Unhandled font-family tag: '%s'" % style)
                elif "font-size" in style:
                    tagtype = self.FONTSIZE
                    match = re.match("font-size:([^;]*)px;", style)
                    if match:
                        arg = int(match.groups()[0])
                    else:
                        print("Unhandled font-size tag: '%s'" % style)
                else:
                    print("Unhandled span arg: '%s'" % attrs)
            else:
                print("span has no style: '%s'" % attrs)
        # "b", "i", "u", "sup": direct conversion
        elif tag == "b":
            tagtype = self.BOLD
        elif tag == "i":
            tagtype = self.ITALIC
        elif tag == "u":
            tagtype = self.UNDERLINE
        elif tag == "sup":
            tagtype = self.SUPERSCRIPT
        elif tag == "p":
            self.__text += "\n\n"
            return
        elif tag == "div":
            self.__text += "\n"
            return
        elif tag == "a":
            tagtype = self.LINK
            # "a": get /object/handle, or url
            if "href" in attrs:
                href = attrs["href"]
                if href.startswith("/"):
                    parts = href.split("/")
                    arg = cuni("gramps://%s/handle/%s") % (parts[-2].title(), parts[-1])
                else:
                    arg = href
            else:
                print("Unhandled a with no href: '%s'" % attrs)
        else:
            return
            print("Unhandled tag: '%s'" % tag)

        if start_pos == len(self.__text): return # does nothing
        key = ((tagtype, ''), arg)
        if key not in self.__tags:
            self.__tags[key] = []
        self.__tags[key].append((start_pos, len(self.__text)))

    def tags(self):
        # [((code, cuni('')), string/num, [(start, stop), ...]), ...]
        return [(key[0], key[1], self.__tags[key]) for key in self.__tags]

    def text(self):
        return self.__text

def parse_styled_text(text):
    parser = WebAppParser()
    text = text.replace("&nbsp;", " ") # otherwise removes them?
    parser.feed(text)
    parser.close()
    return (parser.text(), parser.tags())

def make_log(obj, log_type, last_changed_by, reason, cache):
    """
    Makes a record of the changes performed.
    """
    # Can also add private
    last_changed = datetime.datetime.now()
    log = models.Log(referenced_by=obj,
                     log_type=log_type,
                     order=0,
                     reason=reason,
                     last_changed=last_changed,
                     last_changed_by=last_changed_by,
                     cache=cache)
    log.save()
    
def person_get_birth_date(person):
    #db = DbDjango()
    #event = get_birth_or_fallback(db, db.get_person_from_handle(person.handle))
    #if event:
    #    return event.date
    return None

def person_get_death_date(person):
    #db = DbDjango()
    #event = get_death_or_fallback(db, db.get_person_from_handle(person.handle))
    #if event:
    #    return event.date
    return None

