<commit_before>#!/usr/bin/env python

# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================

args_message = 'GRAPH_CONFIG_FILE...'
help_message = """Produce pretty graphs representing branches and merging.
For each config file specified, construct a graph and write it as a PNG file."""
example = """
  [graph]
  filename = merge-sync-1.png
  title = Sync Merge: CC vs SVN
  # Branches: (branch name, branched from node, first rev, last rev).
  branches = [
    ('A', 'O0', 1, 4),
    ('O', None, 0, 0),
    ('B', 'O0', 1, 5)
    ]
  # Changes: nodes in which a change was committed; merge targets need not
  # be listed here.
  changes = [
    'A1', 'A2', 'A3', 'A4',
    'B1', 'B2', 'B3', 'B4', 'B5'
    ]
  # Merges: (base node, source-right node, target node, label).
  # Base is also known as source-left.
  merges = [
    ('O0', 'A:1', 'B3', 'sync'),
    ('A2', 'A:3', 'B5', 'sync'),
    ]
  # Annotations for nodes: (node, annotation text).
  annotations = [
    ('A2', 'cc:YCA')
    ]
"""

# Notes about different kinds of merge.
#
# A basic 3-way merge is ...
#
# The ClearCase style of merge is a 3-way merge.
#
# The Subversion style of merge (that is, one phase of a Subversion merge)
# is a three-way merge with its base (typically the YCA) on the source branch.

import sys
import pydot
from pydot import Node, Edge

def mergeinfo_to_node_list(mi):
  """Convert a mergeinfo string such as '/foo:1,3-5*' into a list of
     node names such as ['foo1', 'foo3', 'foo4', 'foo5']."""
  ### Doesn't yet strip the leading slash.
  l = []
  if mi:
    for mi_str in mi.split(' '):
      path, ranges = mi_str.split(':')
      for r in ranges.split(','):
        if r.endswith('*'):
          # TODO: store & use this 'non-inheritable' flag
          # Remove the flag
          r = r[:-1]
        rlist = r.split('-')
        r1 = int(rlist[0])
        if len(rlist) == 2:
          r2 = int(rlist[1])
        else:
          r2 = r1
        for rev in range(r1, r2 + 1):
          l.append(path + str(rev))
  return l

class MergeGraph(pydot.Graph):
  """Base class, not intended for direct use.  Use MergeDot for the main
     graph and MergeSubgraph for a subgraph."""

  def mk_origin_node(graph, name):
    """Add a node to the graph"""
    graph.add_node(Node(name + '0', label=name, shape='plaintext'))

  def mk_invis_node(graph, name):
    """Add a node to the graph"""
    graph.add_node(Node(name, style='invis'))

  def mk_node(graph, name):
    """Add a node to the graph, if not already present"""
    if not graph.get_node(name):
      if name in graph.changes:
        graph.add_node(Node(name))
      else:
        graph.add_node(Node(name, color='grey', label=''))

  def mk_merge_target(graph, target_node, important):
    """Add a merge target node to the graph."""
    if important:
      color = 'red'
    else:
      color = 'black'
    graph.add_node(Node(target_node, color=color, fontcolor=color, style='bold'))

  def mk_edge(graph, name1, name2, **attrs):
    """Add an ordinary edge to the graph"""
    graph.add_edge(Edge(name1, name2, dir='none', style='dotted', color='grey', **attrs))

  def mk_br_edge(graph, name1, name2):
    """Add a branch-creation edge to the graph"""
    # Constraint=false to avoid the Y-shape skewing the nice parallel branch lines
    graph.mk_edge(name1, name2, constraint='false')

  def mk_merge_edge(graph, src_node, tgt_node, kind, label, important):
    """Add a merge edge to the graph"""
    if important:
      color = 'red'
    else:
      color = 'grey'
    e = Edge(src_node, tgt_node, constraint='false',
             label='"' + label + '"',
             color=color, fontcolor=color,
             style='bold')
    if kind == 'cherry':
      e.set_style('dashed')
      e.set_dir('both')
      e.set_arrowtail('tee')
    graph.add_edge(e)

  def mk_mergeinfo_edge(graph, base_node, src_node, important):
    """"""
    if important:
      color = 'red'
    else:
      color = 'grey'
    graph.add_edge(Edge(base_node, src_node,
                        dir='both', arrowtail='odot', arrowhead='tee',
                        color=color, constraint='false'))

  def mk_invis_edge(graph, name1, name2):
    """Add an invisible edge to the graph"""
    graph.add_edge(Edge(name1, name2, style='invis'))

  def add_merge(graph, merge, important):
    """Add a merge"""
    base_node, src_node, tgt_node, kind = merge

    if base_node and src_node and kind != 'cherry':
      graph.mk_mergeinfo_edge(base_node, src_node, important)

    # Merge target node
    graph.mk_merge_target(tgt_node, important)

    # Merge edge
    graph.mk_merge_edge(src_node, tgt_node, kind, kind, important)

  def add_annotation(graph, node, label, color='red'):
    """"""
    ann_node = node + '_annotation'
    g = pydot.Subgraph(rank='same')
    g.add_node(Node(ann_node, shape='box', color=color, label='"' + label + '"'))
    g.add_edge(Edge(ann_node, node, style='dotted', color=color, dir='none', constraint='false'))
    graph.add_subgraph(g)

class MergeSubgraph(MergeGraph, pydot.Subgraph):
  """"""
  def __init__(graph, **attrs):
    """"""
    MergeGraph.__init__(graph)
    pydot.Subgraph.__init__(graph, **attrs)

class MergeDot(MergeGraph, pydot.Dot):
  """
  # TODO: In the 'merges' input, find the predecessor automatically.
  """
  def __init__(graph, config_filename, **attrs):
    """Return a new MergeDot graph generated from the specified config file."""
    MergeGraph.__init__(graph)
    pydot.Dot.__init__(graph, **attrs)

    graph.read_config(config_filename)

    graph.construct()

  def read_config(graph, config_filename):
    """"""
    import ConfigParser
    config = ConfigParser.SafeConfigParser()
    files_read = config.read(config_filename)
    if len(files_read) == 0:
      print >> sys.stderr, 'graph: unable to read graph config from "' + config_filename + '"'
      sys.exit(1)
    graph.title = config.get('graph', 'title')
    graph.filename = config.get('graph', 'filename')
    graph.branches = eval(config.get('graph', 'branches'))
    graph.changes = eval(config.get('graph', 'changes'))
    graph.merges = eval(config.get('graph', 'merges'))
    graph.annotations = eval(config.get('graph', 'annotations'))

  def construct(graph):
    """"""
    # Origin nodes (done first, in an attempt to set the order)
    for br, orig, r1, head in graph.branches:
      graph.mk_origin_node(br)

    # Edges and target nodes for merges
    for merge in graph.merges:
      # Emphasize the last merge, as it's the important one
      important = (merge == graph.merges[-1])
      graph.add_merge(merge, important)

    # Edges for basic lines of descent
    for br, orig, r1, head in graph.branches:
      sub_g = MergeSubgraph(ordering='out')
      for i in range(1, head + 1):
        prev_n = br + str(i - 1)
        this_n = br + str(i)

        # Normal edges and nodes
        if i < r1:
          graph.mk_invis_node(this_n)
        else:
          graph.mk_node(this_n)
        if i <= r1:
          graph.mk_invis_edge(prev_n, this_n)
        else:
          graph.mk_edge(prev_n, this_n)

      # Branch creation edges
      if orig:
        sub_g.mk_br_edge(orig, br + str(r1))

      graph.add_subgraph(sub_g)

    # Annotations
    for node, label in graph.annotations:
      graph.add_annotation(node, label)

    # A title for the graph (added last so it goes at the top)
    #graph.add_node(Node('title', shape='plaintext', label='"' + graph.title + '"'))

# If run as a program, process each input filename as a graph config file.
if __name__ == '__main__':
  prog_name = sys.argv[0]
  if len(sys.argv) == 1:
    usage = '%s: usage: "%s %s"' % (prog_name, prog_name, args_message)
    print >> sys.stderr, usage
    sys.exit(1)

  for config_filename in sys.argv[1:]:
    print prog_name + ": reading '" + config_filename + "',",
    graph = MergeDot(config_filename, rankdir='LR')
    print "writing '" + graph.filename + "'"
    graph.write_png(graph.filename)

<commit_msg>Teach merge-graph.py to allow multiple annotations on the same node, and make some minor formatting changes.<commit_after>#!/usr/bin/env python

# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================

args_message = 'GRAPH_CONFIG_FILE...'
help_message = """Produce pretty graphs representing branches and merging.
For each config file specified, construct a graph and write it as a PNG file."""

# Config file format:
example = """
  [graph]
  filename = merge-sync-1.png
  title = Sync Merge: CC vs SVN
  # Branches: (branch name, branched from node, first rev, last rev).
  branches = [
    ('A', 'O0', 1, 4),
    ('O', None, 0, 0),
    ('B', 'O0', 1, 5)
    ]
  # Changes: nodes in which a change was committed; merge targets need not
  # be listed here.
  changes = [
    'A1', 'A2', 'A3', 'A4',
    'B1', 'B2', 'B3', 'B4', 'B5'
    ]
  # Merges: (base node, source-right node, target node, label).
  # Base is also known as source-left.
  merges = [
    ('O0', 'A:1', 'B3', 'sync'),
    ('A2', 'A:3', 'B5', 'sync'),
    ]
  # Annotations for nodes: (node, annotation text).
  annotations = [
    ('A2', 'cc:YCA')
    ]
"""

# Notes about different kinds of merge.
#
# A basic 3-way merge is ...
#
# The ClearCase style of merge is a 3-way merge.
#
# The Subversion style of merge (that is, one phase of a Subversion merge)
# is a three-way merge with its base (typically the YCA) on the source branch.

import sys
import pydot
from pydot import Node, Edge

def mergeinfo_to_node_list(mi):
  """Convert a mergeinfo string such as '/foo:1,3-5*' into a list of
     node names such as ['foo1', 'foo3', 'foo4', 'foo5']."""
  ### Doesn't yet strip the leading slash.
  l = []
  if mi:
    for mi_str in mi.split(' '):
      path, ranges = mi_str.split(':')
      for r in ranges.split(','):
        if r.endswith('*'):
          # TODO: store & use this 'non-inheritable' flag
          # Remove the flag
          r = r[:-1]
        rlist = r.split('-')
        r1 = int(rlist[0])
        if len(rlist) == 2:
          r2 = int(rlist[1])
        else:
          r2 = r1
        for rev in range(r1, r2 + 1):
          l.append(path + str(rev))
  return l

class MergeGraph(pydot.Graph):
  """Base class, not intended for direct use.  Use MergeDot for the main
     graph and MergeSubgraph for a subgraph."""

  def mk_origin_node(graph, name, label):
    """Add a node to the graph"""
    graph.add_node(Node(name, label=label, shape='plaintext'))

  def mk_invis_node(graph, name):
    """Add a node to the graph"""
    graph.add_node(Node(name, style='invis'))

  def mk_node(graph, name, label=None):
    """Add a node to the graph, if not already present"""
    if not graph.get_node(name):
      if not label:
        label = name
      if name in graph.changes:
        graph.add_node(Node(name, label=label))
      else:
        graph.add_node(Node(name, color='grey', label=''))

  def mk_merge_target(graph, target_node, important):
    """Add a merge target node to the graph."""
    if important:
      color = 'red'
    else:
      color = 'black'
    graph.add_node(Node(target_node, color=color, fontcolor=color, style='bold'))

  def mk_edge(graph, name1, name2, **attrs):
    """Add an ordinary edge to the graph"""
    graph.add_edge(Edge(name1, name2, dir='none', style='dotted', color='grey', **attrs))

  def mk_br_edge(graph, name1, name2):
    """Add a branch-creation edge to the graph"""
    # Constraint=false to avoid the Y-shape skewing the nice parallel branch lines
    graph.mk_edge(name1, name2, constraint='false')

  def mk_merge_edge(graph, src_node, tgt_node, kind, label, important):
    """Add a merge edge to the graph"""
    if important:
      color = 'red'
    else:
      color = 'grey'
    e = Edge(src_node, tgt_node, constraint='false',
             label='"' + label + '"',
             color=color, fontcolor=color,
             style='bold')
    if kind == 'cherry':
      e.set_style('dashed')
    graph.add_edge(e)

  def mk_mergeinfo_edge(graph, base_node, src_node, important):
    """"""
    if important:
      color = 'red'
    else:
      color = 'grey'
    graph.add_edge(Edge(base_node, src_node,
                        dir='both', arrowtail='odot', arrowhead='tee',
                        color=color, constraint='false'))

  def mk_invis_edge(graph, name1, name2):
    """Add an invisible edge to the graph"""
    graph.add_edge(Edge(name1, name2, style='invis'))

  def add_merge(graph, merge, important):
    """Add a merge"""
    base_node, src_node, tgt_node, kind = merge

    if base_node and src_node:  # and kind != 'cherry':
      graph.mk_mergeinfo_edge(base_node, src_node, important)

    # Merge target node
    graph.mk_merge_target(tgt_node, important)

    # Merge edge
    graph.mk_merge_edge(src_node, tgt_node, kind, kind, important)

  def add_annotation(graph, node, label, color='red'):
    """Add a graph node that serves as an annotation to a normal node.
       More than one annotation can be added to the same normal node."""
    subg_name = node + '_annotations'

    def get_subgraph(graph, name):
      """Equivalent to pydot.Graph.get_subgraph() when there is no more than
         one subgraph of the given name, but working aroung a bug in
         pydot.Graph.get_subgraph()."""
      for subg in graph.get_subgraph_list():
        if subg.get_name() == name:
          return subg
      return None

    g = get_subgraph(graph, subg_name)
    if not g:
      g = pydot.Subgraph(subg_name, rank='same')
      graph.add_subgraph(g)

    ann_node = node + '_'
    while g.get_node(ann_node):
      ann_node = ann_node + '_'
    g.add_node(Node(ann_node, shape='box', color=color, label='"' + label + '"'))
    g.add_edge(Edge(ann_node, node, style='dotted', color=color, dir='none', constraint='false'))

class MergeSubgraph(MergeGraph, pydot.Subgraph):
  """"""
  def __init__(graph, **attrs):
    """"""
    MergeGraph.__init__(graph)
    pydot.Subgraph.__init__(graph, **attrs)

class MergeDot(MergeGraph, pydot.Dot):
  """
  # TODO: In the 'merges' input, find the predecessor automatically.
  """
  def __init__(graph, config_filename, **attrs):
    """Return a new MergeDot graph generated from a config file."""
    MergeGraph.__init__(graph)
    pydot.Dot.__init__(graph, **attrs)

    graph.read_config(config_filename)

    graph.construct()

  def read_config(graph, config_filename):
    """Initialize a MergeDot graph's input data from a config file."""
    import ConfigParser
    config = ConfigParser.SafeConfigParser()
    files_read = config.read(config_filename)
    if len(files_read) == 0:
      print >> sys.stderr, 'graph: unable to read graph config from "' + config_filename + '"'
      sys.exit(1)
    graph.filename = config.get('graph', 'filename')
    graph.title = config.get('graph', 'title')
    graph.branches = eval(config.get('graph', 'branches'))
    graph.changes = eval(config.get('graph', 'changes'))
    graph.merges = eval(config.get('graph', 'merges'))
    graph.annotations = eval(config.get('graph', 'annotations'))

  def construct(graph):
    """"""
    # Origin nodes (done first, in an attempt to set the order)
    for br, orig, r1, head in graph.branches:
      name = br + '0'
      if r1 > 0:
        graph.mk_origin_node(name, br)
      else:
        graph.mk_node(name, label=br)

    # Edges and target nodes for merges
    for merge in graph.merges:
      # Emphasize the last merge, as it's the important one
      important = (merge == graph.merges[-1])
      graph.add_merge(merge, important)

    # Parallel edges for basic lines of descent
    for br, orig, r1, head in graph.branches:
      sub_g = MergeSubgraph(ordering='out')
      for i in range(1, head + 1):
        prev_n = br + str(i - 1)
        this_n = br + str(i)

        # Normal edges and nodes
        if i < r1:
          graph.mk_invis_node(this_n)
        else:
          graph.mk_node(this_n)
        if i <= r1:
          graph.mk_invis_edge(prev_n, this_n)
        else:
          graph.mk_edge(prev_n, this_n)

      # Branch creation edges
      if orig:
        sub_g.mk_br_edge(orig, br + str(r1))

      graph.add_subgraph(sub_g)

    # Annotations
    for node, label in graph.annotations:
      graph.add_annotation(node, label)

    # A title for the graph (added last so it goes at the top)
    #if graph.title:
    #  graph.add_node(Node('title', shape='plaintext', label='"' + graph.title + '"'))

# If run as a program, process each input filename as a graph config file.
if __name__ == '__main__':
  prog_name = sys.argv[0]
  if len(sys.argv) == 1:
    usage = '%s: usage: "%s %s"' % (prog_name, prog_name, args_message)
    print >> sys.stderr, usage
    sys.exit(1)

  for config_filename in sys.argv[1:]:
    print prog_name + ": reading '" + config_filename + "',",
    graph = MergeDot(config_filename, rankdir='LR')
    print "writing '" + graph.filename + "'"
    graph.write_png(graph.filename)

<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# which-error.py: Print semantic Subversion error code names mapped from
#                 their numeric error code values
#
# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================
#
# $HeadURL$
# $LastChangedDate$
# $LastChangedBy$
# $LastChangedRevision$
#

import errno
import sys
import os.path
import re

try:
  from svn import core
except ImportError, e:
  sys.stderr.write("ERROR: Unable to import Subversion's Python bindings: '%s'\n" \
                   "Hint: Set your PYTHONPATH environment variable, or adjust your " \
                   "PYTHONSTARTUP\nfile to point to your Subversion install " \
                   "location's svn-python directory.\n" % e)
  sys.stderr.flush()
  sys.exit(1)

def usage_and_exit():
  progname = os.path.basename(sys.argv[0])
  sys.stderr.write("""Usage: 1. %s ERRNUM [...]
       2. %s parse
       3. %s list

Print numeric and semantic error code information for Subversion error
codes.  This can be done in variety of ways:

   1. For each ERRNUM, list the error code information.

   2. Parse standard input as if it was error stream from a debug-mode
      Subversion command-line client, echoing that input to stdout,
      followed by the error code information for codes found in use in
      that error stream.

   3. Simply list the error code information for all known such
      mappings.

""" % (progname, progname, progname))
  sys.exit(1)

def get_errors():
  errs = {}
  ## errno values.
  errs.update(errno.errorcode)
  ## APR-defined errors, from apr_errno.h.
  dirname = os.path.dirname(os.path.realpath(__file__))
  for line in open(os.path.join(dirname, 'aprerr.txt')):
    # aprerr.txt parsing duplicated in gen_base.py:write_errno_table()
    if line.startswith('#'):
       continue
    key, _, val = line.split()
    errs[int(val)] = key
  ## Subversion errors, from svn_error_codes.h.
  for key in vars(core):
    if key.find('SVN_ERR_') == 0:
      try:
        val = int(vars(core)[key])
        errs[val] = key
      except:
        pass
  return errs

def print_error(code):
  try:
    print('%08d  %s' % (code, __svn_error_codes[code]))
  except KeyError:
    if code == -41:
      print("Sit by a lake.")
    elif code >= 120100 and code < 121000:
      print('%08d  <error code from libserf; see serf.h>')
    else:
      print('%08d  *** UNKNOWN ERROR CODE ***' % (code))

if __name__ == "__main__":
  global __svn_error_codes
  __svn_error_codes = get_errors()
  codes = []
  if len(sys.argv) < 2:
    usage_and_exit()

  # Get a list of known codes
  if sys.argv[1] == 'list':
    if len(sys.argv) > 2:
      usage_and_exit()
    codes = sorted(__svn_error_codes.keys())

  # Get a list of code by parsing stdin for apr_err=CODE instances
  elif sys.argv[1] == 'parse':
    if len(sys.argv) > 2:
      usage_and_exit()
    while True:
      line = sys.stdin.readline()
      if not line:
        break
      sys.stdout.write(line)
      match = re.match(r'^.*apr_err=([0-9]+)[^0-9].*$', line)
      if match:
        codes.append(int(match.group(1)))

  # Get the list of requested codes
  else:
    for code in sys.argv[1:]:
      try:
        code = code.lstrip('EW')
        codes.append(int(code))
      except ValueError:
        usage_and_exit()

  # Print the harvest codes
  for code in codes:
    print_error(code)

<commit_msg>* tools/dev/which-error.py   (print_error): Followup to r1727512, include the code.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# which-error.py: Print semantic Subversion error code names mapped from
#                 their numeric error code values
#
# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================
#
# $HeadURL$
# $LastChangedDate$
# $LastChangedBy$
# $LastChangedRevision$
#

import errno
import sys
import os.path
import re

try:
  from svn import core
except ImportError, e:
  sys.stderr.write("ERROR: Unable to import Subversion's Python bindings: '%s'\n" \
                   "Hint: Set your PYTHONPATH environment variable, or adjust your " \
                   "PYTHONSTARTUP\nfile to point to your Subversion install " \
                   "location's svn-python directory.\n" % e)
  sys.stderr.flush()
  sys.exit(1)

def usage_and_exit():
  progname = os.path.basename(sys.argv[0])
  sys.stderr.write("""Usage: 1. %s ERRNUM [...]
       2. %s parse
       3. %s list

Print numeric and semantic error code information for Subversion error
codes.  This can be done in variety of ways:

   1. For each ERRNUM, list the error code information.

   2. Parse standard input as if it was error stream from a debug-mode
      Subversion command-line client, echoing that input to stdout,
      followed by the error code information for codes found in use in
      that error stream.

   3. Simply list the error code information for all known such
      mappings.

""" % (progname, progname, progname))
  sys.exit(1)

def get_errors():
  errs = {}
  ## errno values.
  errs.update(errno.errorcode)
  ## APR-defined errors, from apr_errno.h.
  dirname = os.path.dirname(os.path.realpath(__file__))
  for line in open(os.path.join(dirname, 'aprerr.txt')):
    # aprerr.txt parsing duplicated in gen_base.py:write_errno_table()
    if line.startswith('#'):
       continue
    key, _, val = line.split()
    errs[int(val)] = key
  ## Subversion errors, from svn_error_codes.h.
  for key in vars(core):
    if key.find('SVN_ERR_') == 0:
      try:
        val = int(vars(core)[key])
        errs[val] = key
      except:
        pass
  return errs

def print_error(code):
  try:
    print('%08d  %s' % (code, __svn_error_codes[code]))
  except KeyError:
    if code == -41:
      print("Sit by a lake.")
    elif code >= 120100 and code < 121000:
      print('%08d  <error code from libserf; see serf.h>' % (code))
    else:
      print('%08d  *** UNKNOWN ERROR CODE ***' % (code))

if __name__ == "__main__":
  global __svn_error_codes
  __svn_error_codes = get_errors()
  codes = []
  if len(sys.argv) < 2:
    usage_and_exit()

  # Get a list of known codes
  if sys.argv[1] == 'list':
    if len(sys.argv) > 2:
      usage_and_exit()
    codes = sorted(__svn_error_codes.keys())

  # Get a list of code by parsing stdin for apr_err=CODE instances
  elif sys.argv[1] == 'parse':
    if len(sys.argv) > 2:
      usage_and_exit()
    while True:
      line = sys.stdin.readline()
      if not line:
        break
      sys.stdout.write(line)
      match = re.match(r'^.*apr_err=([0-9]+)[^0-9].*$', line)
      if match:
        codes.append(int(match.group(1)))

  # Get the list of requested codes
  else:
    for code in sys.argv[1:]:
      try:
        code = code.lstrip('EW')
        codes.append(int(code))
      except ValueError:
        usage_and_exit()

  # Print the harvest codes
  for code in codes:
    print_error(code)

<|endoftext|>


<commit_before>#!/usr/bin/python2.4
# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

'''SCons integration for GRIT.
'''

# NOTE:  DO NOT IMPORT ANY GRIT STUFF HERE - we import lazily so that grit and
# its dependencies aren't imported until actually needed.

import os
import types

def _IsDebugEnabled():
  return 'GRIT_DEBUG' in os.environ and os.environ['GRIT_DEBUG'] == '1'

def _SourceToFile(source):
  '''Return the path to the source file, given the 'source' argument as provided
  by SCons to the _Builder or _Emitter functions.
  '''
  # Get the filename of the source.  The 'source' parameter can be a string,
  # a "node", or a list of strings or nodes.
  if isinstance(source, types.ListType):
    # TODO(gspencer):  Had to add the .rfile() method to the following
    # line to get this to work with Repository() directories.
    # Get this functionality folded back into the upstream grit tool.
    #source = str(source[0])
    source = str(source[0].rfile())
  else:
    # TODO(gspencer):  Had to add the .rfile() method to the following
    # line to get this to work with Repository() directories.
    # Get this functionality folded back into the upstream grit tool.
    #source = str(source))
    source = str(source.rfile())
  return source

def _Builder(target, source, env):
  from grit import grit_runner
  from grit.tool import build
  options = grit_runner.Options()
  # This sets options to default values  TODO(joi) Remove verbose
  options.ReadOptions(['-v'])
  options.input = _SourceToFile(source)

  # TODO(joi) Check if we can get the 'verbose' option from the environment.

  builder = build.RcBuilder()

  # Get the CPP defines from the environment.
  for flag in env.get('RCFLAGS', []):
    if flag.startswith('/D'):
      flag = flag[2:]
    name, val = build.ParseDefine(flag)
    # Only apply to first instance of a given define
    if name not in builder.defines:
      builder.defines[name] = val

  # To ensure that our output files match what we promised SCons, we
  # use the list of targets provided by SCons and update the file paths in
  # our .grd input file with the targets.
  builder.scons_targets = [str(t) for t in target]
  builder.Run(options, [])
  return None  # success

def _Emitter(target, source, env):
  '''A SCons emitter for .grd files, which modifies the list of targes to
  include all files in the <outputs> section of the .grd file as well as
  any other files output by 'grit build' for the .grd file.
  '''
  from grit import util
  from grit import grd_reader

  # TODO(gspencer):  Had to use .abspath, not str(target[0]), to get
  # this to work with Repository() directories.
  # Get this functionality folded back into the upstream grit tool.
  #base_dir = util.dirname(str(target[0]))
  base_dir = util.dirname(target[0].abspath)

  grd = grd_reader.Parse(_SourceToFile(source), debug=_IsDebugEnabled())

  target = []
  lang_folders = {}
  # Add all explicitly-specified output files
  for output in grd.GetOutputFiles():
    path = os.path.join(base_dir, output.GetFilename())
    target.append(path)
    if _IsDebugEnabled():
      print "GRIT: Added target %s" % path
    if output.attrs['lang'] != '':
      lang_folders[output.attrs['lang']] = os.path.dirname(path)

  # Add all generated files, once for each output language.
  for node in grd:
    if node.name == 'structure':
      # TODO(joi) Should remove the "if sconsdep is true" thing as it is a
      # hack - see grit/node/structure.py
      if node.HasFileForLanguage() and node.attrs['sconsdep'] == 'true':
        for lang in lang_folders:
          path = node.FileForLanguage(lang, lang_folders[lang],
                                      create_file=False,
                                      return_if_not_generated=False)
          if path:
            target.append(path)
            if _IsDebugEnabled():
              print "GRIT: Added target %s" % path

  # return target and source lists
  return (target, source)

def _Scanner(file_node, env, path):
  '''A SCons scanner function for .grd files, which outputs the list of files
  that changes in could change the output of building the .grd file.
  '''
  from grit import grd_reader

  # TODO(gspencer):  Had to add the .rfile() method to the following
  # line to get this to work with Repository() directories.
  # Get this functionality folded back into the upstream grit tool.
  #grd = grd_reader.Parse(str(file_node)), debug=_IsDebugEnabled())
  grd = grd_reader.Parse(str(file_node.rfile()), debug=_IsDebugEnabled())
  files = []
  for node in grd:
    if (node.name == 'structure' or node.name == 'skeleton' or
        (node.name == 'file' and node.parent and
         node.parent.name == 'translations')):
      files.append(os.path.abspath(node.GetFilePath()))
  return files

# Function name is mandated by newer versions of SCons.
def generate(env):
  # Importing this module should be possible whenever this function is invoked
  # since it should only be invoked by SCons.
  import SCons.Builder
  import SCons.Action

  # The varlist parameter tells SCons that GRIT needs to be invoked again
  # if RCFLAGS has changed since last compilation.
  action = SCons.Action.FunctionAction(_Builder, varlist=['RCFLAGS'])

  builder = SCons.Builder.Builder(action=action,
                              emitter=_Emitter,
                              src_suffix='.grd')

  scanner = env.Scanner(function=_Scanner, name='GRIT', skeys=['.grd'])

  # add our builder and scanner to the environment
  env.Append(BUILDERS = {'GRIT': builder})
  env.Prepend(SCANNERS = scanner)

# Function name is mandated by newer versions of SCons.
def exists(env):
  return 1

<commit_msg>Use the public SCons API to create the GRIT Builder and Action, not by reaching directly into the SCons internals. Review URL: http://codereview.chromium.org/11242<commit_after>#!/usr/bin/python2.4
# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

'''SCons integration for GRIT.
'''

# NOTE:  DO NOT IMPORT ANY GRIT STUFF HERE - we import lazily so that grit and
# its dependencies aren't imported until actually needed.

import os
import types

def _IsDebugEnabled():
  return 'GRIT_DEBUG' in os.environ and os.environ['GRIT_DEBUG'] == '1'

def _SourceToFile(source):
  '''Return the path to the source file, given the 'source' argument as provided
  by SCons to the _Builder or _Emitter functions.
  '''
  # Get the filename of the source.  The 'source' parameter can be a string,
  # a "node", or a list of strings or nodes.
  if isinstance(source, types.ListType):
    # TODO(gspencer):  Had to add the .rfile() method to the following
    # line to get this to work with Repository() directories.
    # Get this functionality folded back into the upstream grit tool.
    #source = str(source[0])
    source = str(source[0].rfile())
  else:
    # TODO(gspencer):  Had to add the .rfile() method to the following
    # line to get this to work with Repository() directories.
    # Get this functionality folded back into the upstream grit tool.
    #source = str(source))
    source = str(source.rfile())
  return source

def _Builder(target, source, env):
  from grit import grit_runner
  from grit.tool import build
  options = grit_runner.Options()
  # This sets options to default values  TODO(joi) Remove verbose
  options.ReadOptions(['-v'])
  options.input = _SourceToFile(source)

  # TODO(joi) Check if we can get the 'verbose' option from the environment.

  builder = build.RcBuilder()

  # Get the CPP defines from the environment.
  for flag in env.get('RCFLAGS', []):
    if flag.startswith('/D'):
      flag = flag[2:]
    name, val = build.ParseDefine(flag)
    # Only apply to first instance of a given define
    if name not in builder.defines:
      builder.defines[name] = val

  # To ensure that our output files match what we promised SCons, we
  # use the list of targets provided by SCons and update the file paths in
  # our .grd input file with the targets.
  builder.scons_targets = [str(t) for t in target]
  builder.Run(options, [])
  return None  # success

def _Emitter(target, source, env):
  '''A SCons emitter for .grd files, which modifies the list of targes to
  include all files in the <outputs> section of the .grd file as well as
  any other files output by 'grit build' for the .grd file.
  '''
  from grit import util
  from grit import grd_reader

  # TODO(gspencer):  Had to use .abspath, not str(target[0]), to get
  # this to work with Repository() directories.
  # Get this functionality folded back into the upstream grit tool.
  #base_dir = util.dirname(str(target[0]))
  base_dir = util.dirname(target[0].abspath)

  grd = grd_reader.Parse(_SourceToFile(source), debug=_IsDebugEnabled())

  target = []
  lang_folders = {}
  # Add all explicitly-specified output files
  for output in grd.GetOutputFiles():
    path = os.path.join(base_dir, output.GetFilename())
    target.append(path)
    if _IsDebugEnabled():
      print "GRIT: Added target %s" % path
    if output.attrs['lang'] != '':
      lang_folders[output.attrs['lang']] = os.path.dirname(path)

  # Add all generated files, once for each output language.
  for node in grd:
    if node.name == 'structure':
      # TODO(joi) Should remove the "if sconsdep is true" thing as it is a
      # hack - see grit/node/structure.py
      if node.HasFileForLanguage() and node.attrs['sconsdep'] == 'true':
        for lang in lang_folders:
          path = node.FileForLanguage(lang, lang_folders[lang],
                                      create_file=False,
                                      return_if_not_generated=False)
          if path:
            target.append(path)
            if _IsDebugEnabled():
              print "GRIT: Added target %s" % path

  # return target and source lists
  return (target, source)

def _Scanner(file_node, env, path):
  '''A SCons scanner function for .grd files, which outputs the list of files
  that changes in could change the output of building the .grd file.
  '''
  from grit import grd_reader

  # TODO(gspencer):  Had to add the .rfile() method to the following
  # line to get this to work with Repository() directories.
  # Get this functionality folded back into the upstream grit tool.
  #grd = grd_reader.Parse(str(file_node)), debug=_IsDebugEnabled())
  grd = grd_reader.Parse(str(file_node.rfile()), debug=_IsDebugEnabled())
  files = []
  for node in grd:
    if (node.name == 'structure' or node.name == 'skeleton' or
        (node.name == 'file' and node.parent and
         node.parent.name == 'translations')):
      files.append(os.path.abspath(node.GetFilePath()))
  return files

# Function name is mandated by newer versions of SCons.
def generate(env):
  # The varlist parameter tells SCons that GRIT needs to be invoked again
  # if RCFLAGS has changed since last compilation.

  # TODO(gspencer):  change to use the public SCons API Action()
  # and Builder(), instead of reaching directly into internal APIs.
  # Get this change folded back into the upstream grit tool.
  action = env.Action(_Builder, varlist=['RCFLAGS'])

  builder = env.Builder(action=action, emitter=_Emitter, src_suffix='.grd')

  scanner = env.Scanner(function=_Scanner, name='GRIT', skeys=['.grd'])

  # add our builder and scanner to the environment
  env.Append(BUILDERS = {'GRIT': builder})
  env.Prepend(SCANNERS = scanner)

# Function name is mandated by newer versions of SCons.
def exists(env):
  return 1

<|endoftext|>


<commit_before>import base64
import ipaddress

try:
    import collections.abc as collections_abc  # only works on python 3.3+
except ImportError:
    import collections as collections_abc

from datetime import date, datetime

from dateutil import parser, tz
from six import string_types, iteritems
from six.moves import map

from .query import Q
from .utils import DslBase, AttrDict, AttrList
from .exceptions import ValidationException
from .wrappers import Range

unicode = type(u'')

def construct_field(name_or_field, **params):
    # {"type": "text", "analyzer": "snowball"}
    if isinstance(name_or_field, collections_abc.Mapping):
        if params:
            raise ValueError('construct_field() cannot accept parameters when passing in a dict.')
        params = name_or_field.copy()
        if 'type' not in params:
            # inner object can be implicitly defined
            if 'properties' in params:
                name = 'object'
            else:
                raise ValueError('construct_field() needs to have a "type" key.')
        else:
            name = params.pop('type')
        return Field.get_dsl_class(name)(**params)

    # Text()
    if isinstance(name_or_field, Field):
        if params:
            raise ValueError('construct_field() cannot accept parameters when passing in a construct_field object.')
        return name_or_field

    # "text", analyzer="snowball"
    return Field.get_dsl_class(name_or_field)(**params)

class Field(DslBase):
    _type_name = 'field'
    _type_shortcut = staticmethod(construct_field)
    # all fields can be multifields
    _param_defs = {'fields': {'type': 'field', 'hash': True}}
    name = None
    _coerce = False

    def __init__(self, multi=False, required=False, *args, **kwargs):
        """
        :arg bool multi: specifies whether field can contain array of values
        :arg bool required: specifies whether field is required
        """
        self._multi = multi
        self._required = required
        super(Field, self).__init__(*args, **kwargs)

    def __getitem__(self, subfield):
        return self._params.get('fields', {})[subfield]

    def _serialize(self, data):
        return data

    def _deserialize(self, data):
        return data

    def _empty(self):
        return None

    def empty(self):
        if self._multi:
            return AttrList([])
        return self._empty()

    def serialize(self, data):
        if isinstance(data, (list, AttrList)):
            return list(map(self._serialize, data))
        return self._serialize(data)

    def deserialize(self, data):
        if isinstance(data, (list, AttrList)):
            data[:] = [
                None if d is None else self._deserialize(d)
                for d in data
            ]
            return data
        if data is None:
            return None
        return self._deserialize(data)

    def clean(self, data):
        if data is not None:
            data = self.deserialize(data)
        if data in (None, [], {}) and self._required:
            raise ValidationException("Value required for this field.")
        return data

    def to_dict(self):
        d = super(Field, self).to_dict()
        name, value = d.popitem()
        value['type'] = name
        return value

class CustomField(Field):
    name = 'custom'
    _coerce = True

    def to_dict(self):
        if isinstance(self.builtin_type, Field):
            return self.builtin_type.to_dict()

        d = super(CustomField, self).to_dict()
        d['type'] = self.builtin_type
        return d

class Object(Field):
    name = 'object'
    _coerce = True

    def __init__(self, doc_class=None, dynamic=None, properties=None, **kwargs):
        """
        :arg document.InnerDoc doc_class: base doc class that handles mapping.
            If no `doc_class` is provided, new instance of `InnerDoc` will be created,
            populated with `properties` and used. Can not be provided together with `properties`
        :arg dynamic: whether new properties may be created dynamically.
            Valid values are `True`, `False`, `'strict'`.
            Can not be provided together with `doc_class`.
            See https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html
            for more details
        :arg dict properties: used to construct underlying mapping if no `doc_class` is provided.
            Can not be provided together with `doc_class`
        """
        if doc_class and (properties or dynamic is not None):
            raise ValidationException(
                'doc_class and properties/dynamic should not be provided together')
        if doc_class:
            self._doc_class = doc_class
        else:
            # FIXME import
            from .document import InnerDoc
            # no InnerDoc subclass, creating one instead...
            self._doc_class = type('InnerDoc', (InnerDoc, ), {})
            for name, field in iteritems(properties or {}):
                self._doc_class._doc_type.mapping.field(name, field)
            if dynamic is not None:
                self._doc_class._doc_type.mapping.meta('dynamic', dynamic)

        self._mapping = self._doc_class._doc_type.mapping
        super(Object, self).__init__(**kwargs)

    def __getitem__(self, name):
        return self._mapping[name]

    def __contains__(self, name):
        return name in self._mapping

    def _empty(self):
        return self._wrap({})

    def _wrap(self, data):
        return self._doc_class.from_es(data, data_only=True)

    def empty(self):
        if self._multi:
            return AttrList([], self._wrap)
        return self._empty()

    def to_dict(self):
        d = self._mapping.to_dict()
        _, d = d.popitem()
        d.update(super(Object, self).to_dict())
        return d

    def _collect_fields(self):
        return self._mapping.properties._collect_fields()

    def _deserialize(self, data):
        # don't wrap already wrapped data
        if isinstance(data, self._doc_class):
            return data

        if isinstance(data, AttrDict):
            data = data._d_

        return self._wrap(data)

    def _serialize(self, data):
        if data is None:
            return None

        # somebody assigned raw dict to the field, we should tolerate that
        if isinstance(data, collections_abc.Mapping):
            return data

        return data.to_dict()

    def clean(self, data):
        data = super(Object, self).clean(data)
        if data is None:
            return None
        if isinstance(data, (list, AttrList)):
            for d in data:
                d.full_clean()
        else:
            data.full_clean()
        return data

    def update(self, other):
        if not isinstance(other, Object):
            # not an inner/nested object, no merge possible
            return

        self._mapping.update(other._mapping)

class Nested(Object):
    name = 'nested'

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('multi', True)
        super(Nested, self).__init__(*args, **kwargs)

class Date(Field):
    name = 'date'
    _coerce = True

    def __init__(self, default_timezone=None, *args, **kwargs):
        """
        :arg default_timezone: timezone that will be automatically used for tz-naive values
            May be instance of `datetime.tzinfo` or string containing TZ offset
        """
        self._default_timezone = default_timezone
        if isinstance(self._default_timezone, string_types):
            self._default_timezone = tz.gettz(self._default_timezone)
        super(Date, self).__init__(*args, **kwargs)

    def _deserialize(self, data):
        if isinstance(data, string_types):
            try:
                data = parser.parse(data)
            except Exception as e:
                raise ValidationException('Could not parse date from the value (%r)' % data, e)

        if isinstance(data, datetime):
            if self._default_timezone and data.tzinfo is None:
                data = data.replace(tzinfo=self._default_timezone)
            return data
        if isinstance(data, date):
            return data
        if isinstance(data, int):
            # Divide by a float to preserve milliseconds on the datetime.
            return datetime.utcfromtimestamp(data / 1000.0)

        raise ValidationException('Could not parse date from the value (%r)' % data)

class Text(Field):
    _param_defs = {
        'fields': {'type': 'field', 'hash': True},
        'analyzer': {'type': 'analyzer'},
        'search_analyzer': {'type': 'analyzer'},
        'search_quote_analyzer': {'type': 'analyzer'},
    }
    name = 'text'

class Keyword(Field):
    _param_defs = {
        'fields': {'type': 'field', 'hash': True},
        'search_analyzer': {'type': 'analyzer'},
        'normalizer': {'type': 'normalizer'}
    }
    name = 'keyword'

class Boolean(Field):
    name = 'boolean'
    _coerce = True

    def _deserialize(self, data):
        if data == "false":
            return False
        return bool(data)

    def clean(self, data):
        if data is not None:
            data = self.deserialize(data)
        if data is None and self._required:
            raise ValidationException("Value required for this field.")
        return data

class Float(Field):
    name = 'float'
    _coerce = True

    def _deserialize(self, data):
        return float(data)

class HalfFloat(Float):
    name = 'half_float'

class ScaledFloat(Float):
    name = 'scaled_float'

    def __init__(self, scaling_factor, *args, **kwargs):
        super(ScaledFloat, self).__init__(scaling_factor=scaling_factor, *args, **kwargs)

class Double(Float):
    name = 'double'

class Integer(Field):
    name = 'integer'
    _coerce = True

    def _deserialize(self, data):
        return int(data)

class Byte(Integer):
    name = 'byte'

class Short(Integer):
    name = 'short'

class Long(Integer):
    name = 'long'

class Ip(Field):
    name = 'ip'
    _coerce = True

    def _deserialize(self, data):
        # the ipaddress library for pypy, python2.5 and 2.6 only accepts unicode.
        return ipaddress.ip_address(unicode(data))

    def _serialize(self, data):
        if data is None:
            return None
        return str(data)

class Binary(Field):
    name = 'binary'
    _coerce = True

    def clean(self, data):
        # Binary fields are opaque, so there's not much cleaning
        # that can be done.
        return data

    def _deserialize(self, data):
        return base64.b64decode(data)

    def _serialize(self, data):
        if data is None:
            return None
        return base64.b64encode(data).decode()

class GeoPoint(Field):
    name = 'geo_point'

class GeoShape(Field):
    name = 'geo_shape'

class Completion(Field):
    _param_defs = {
        'analyzer': {'type': 'analyzer'},
    }
    name = 'completion'

class Percolator(Field):
    name = 'percolator'
    _coerce = True

    def _deserialize(self, data):
        return Q(data)

    def _serialize(self, data):
        if data is None:
            return None
        return data.to_dict()

class RangeField(Field):
    _coerce = True

    def _deserialize(self, data):
        return Range(data)

    def _serialize(self, data):
        if data is None:
            return None
        if isinstance(data, collections_abc.Mapping):
            return data
        return data.to_dict()

class IntegerRange(RangeField):
    name = 'integer_range'

class FloatRange(RangeField):
    name = 'float_range'

class LongRange(RangeField):
    name = 'long_range'

class DoubleRange(RangeField):
    name = 'double_ranged'

class DateRange(RangeField):
    name = 'date_range'

class IpRange(Field):
    # not a RangeField since ip_range supports CIDR ranges
    name = 'ip_range'

class Join(Field):
    name = 'join'

class TokenCount(Field):
    name = 'token_count'

class Murmur3(Field):
    name = 'murmur3'
<commit_msg>Add search_analyzer.<commit_after>import base64
import ipaddress

try:
    import collections.abc as collections_abc  # only works on python 3.3+
except ImportError:
    import collections as collections_abc

from datetime import date, datetime

from dateutil import parser, tz
from six import string_types, iteritems
from six.moves import map

from .query import Q
from .utils import DslBase, AttrDict, AttrList
from .exceptions import ValidationException
from .wrappers import Range

unicode = type(u'')

def construct_field(name_or_field, **params):
    # {"type": "text", "analyzer": "snowball"}
    if isinstance(name_or_field, collections_abc.Mapping):
        if params:
            raise ValueError('construct_field() cannot accept parameters when passing in a dict.')
        params = name_or_field.copy()
        if 'type' not in params:
            # inner object can be implicitly defined
            if 'properties' in params:
                name = 'object'
            else:
                raise ValueError('construct_field() needs to have a "type" key.')
        else:
            name = params.pop('type')
        return Field.get_dsl_class(name)(**params)

    # Text()
    if isinstance(name_or_field, Field):
        if params:
            raise ValueError('construct_field() cannot accept parameters when passing in a construct_field object.')
        return name_or_field

    # "text", analyzer="snowball"
    return Field.get_dsl_class(name_or_field)(**params)

class Field(DslBase):
    _type_name = 'field'
    _type_shortcut = staticmethod(construct_field)
    # all fields can be multifields
    _param_defs = {'fields': {'type': 'field', 'hash': True}}
    name = None
    _coerce = False

    def __init__(self, multi=False, required=False, *args, **kwargs):
        """
        :arg bool multi: specifies whether field can contain array of values
        :arg bool required: specifies whether field is required
        """
        self._multi = multi
        self._required = required
        super(Field, self).__init__(*args, **kwargs)

    def __getitem__(self, subfield):
        return self._params.get('fields', {})[subfield]

    def _serialize(self, data):
        return data

    def _deserialize(self, data):
        return data

    def _empty(self):
        return None

    def empty(self):
        if self._multi:
            return AttrList([])
        return self._empty()

    def serialize(self, data):
        if isinstance(data, (list, AttrList)):
            return list(map(self._serialize, data))
        return self._serialize(data)

    def deserialize(self, data):
        if isinstance(data, (list, AttrList)):
            data[:] = [
                None if d is None else self._deserialize(d)
                for d in data
            ]
            return data
        if data is None:
            return None
        return self._deserialize(data)

    def clean(self, data):
        if data is not None:
            data = self.deserialize(data)
        if data in (None, [], {}) and self._required:
            raise ValidationException("Value required for this field.")
        return data

    def to_dict(self):
        d = super(Field, self).to_dict()
        name, value = d.popitem()
        value['type'] = name
        return value

class CustomField(Field):
    name = 'custom'
    _coerce = True

    def to_dict(self):
        if isinstance(self.builtin_type, Field):
            return self.builtin_type.to_dict()

        d = super(CustomField, self).to_dict()
        d['type'] = self.builtin_type
        return d

class Object(Field):
    name = 'object'
    _coerce = True

    def __init__(self, doc_class=None, dynamic=None, properties=None, **kwargs):
        """
        :arg document.InnerDoc doc_class: base doc class that handles mapping.
            If no `doc_class` is provided, new instance of `InnerDoc` will be created,
            populated with `properties` and used. Can not be provided together with `properties`
        :arg dynamic: whether new properties may be created dynamically.
            Valid values are `True`, `False`, `'strict'`.
            Can not be provided together with `doc_class`.
            See https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html
            for more details
        :arg dict properties: used to construct underlying mapping if no `doc_class` is provided.
            Can not be provided together with `doc_class`
        """
        if doc_class and (properties or dynamic is not None):
            raise ValidationException(
                'doc_class and properties/dynamic should not be provided together')
        if doc_class:
            self._doc_class = doc_class
        else:
            # FIXME import
            from .document import InnerDoc
            # no InnerDoc subclass, creating one instead...
            self._doc_class = type('InnerDoc', (InnerDoc, ), {})
            for name, field in iteritems(properties or {}):
                self._doc_class._doc_type.mapping.field(name, field)
            if dynamic is not None:
                self._doc_class._doc_type.mapping.meta('dynamic', dynamic)

        self._mapping = self._doc_class._doc_type.mapping
        super(Object, self).__init__(**kwargs)

    def __getitem__(self, name):
        return self._mapping[name]

    def __contains__(self, name):
        return name in self._mapping

    def _empty(self):
        return self._wrap({})

    def _wrap(self, data):
        return self._doc_class.from_es(data, data_only=True)

    def empty(self):
        if self._multi:
            return AttrList([], self._wrap)
        return self._empty()

    def to_dict(self):
        d = self._mapping.to_dict()
        _, d = d.popitem()
        d.update(super(Object, self).to_dict())
        return d

    def _collect_fields(self):
        return self._mapping.properties._collect_fields()

    def _deserialize(self, data):
        # don't wrap already wrapped data
        if isinstance(data, self._doc_class):
            return data

        if isinstance(data, AttrDict):
            data = data._d_

        return self._wrap(data)

    def _serialize(self, data):
        if data is None:
            return None

        # somebody assigned raw dict to the field, we should tolerate that
        if isinstance(data, collections_abc.Mapping):
            return data

        return data.to_dict()

    def clean(self, data):
        data = super(Object, self).clean(data)
        if data is None:
            return None
        if isinstance(data, (list, AttrList)):
            for d in data:
                d.full_clean()
        else:
            data.full_clean()
        return data

    def update(self, other):
        if not isinstance(other, Object):
            # not an inner/nested object, no merge possible
            return

        self._mapping.update(other._mapping)

class Nested(Object):
    name = 'nested'

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('multi', True)
        super(Nested, self).__init__(*args, **kwargs)

class Date(Field):
    name = 'date'
    _coerce = True

    def __init__(self, default_timezone=None, *args, **kwargs):
        """
        :arg default_timezone: timezone that will be automatically used for tz-naive values
            May be instance of `datetime.tzinfo` or string containing TZ offset
        """
        self._default_timezone = default_timezone
        if isinstance(self._default_timezone, string_types):
            self._default_timezone = tz.gettz(self._default_timezone)
        super(Date, self).__init__(*args, **kwargs)

    def _deserialize(self, data):
        if isinstance(data, string_types):
            try:
                data = parser.parse(data)
            except Exception as e:
                raise ValidationException('Could not parse date from the value (%r)' % data, e)

        if isinstance(data, datetime):
            if self._default_timezone and data.tzinfo is None:
                data = data.replace(tzinfo=self._default_timezone)
            return data
        if isinstance(data, date):
            return data
        if isinstance(data, int):
            # Divide by a float to preserve milliseconds on the datetime.
            return datetime.utcfromtimestamp(data / 1000.0)

        raise ValidationException('Could not parse date from the value (%r)' % data)

class Text(Field):
    _param_defs = {
        'fields': {'type': 'field', 'hash': True},
        'analyzer': {'type': 'analyzer'},
        'search_analyzer': {'type': 'analyzer'},
        'search_quote_analyzer': {'type': 'analyzer'},
    }
    name = 'text'

class Keyword(Field):
    _param_defs = {
        'fields': {'type': 'field', 'hash': True},
        'search_analyzer': {'type': 'analyzer'},
        'normalizer': {'type': 'normalizer'}
    }
    name = 'keyword'

class Boolean(Field):
    name = 'boolean'
    _coerce = True

    def _deserialize(self, data):
        if data == "false":
            return False
        return bool(data)

    def clean(self, data):
        if data is not None:
            data = self.deserialize(data)
        if data is None and self._required:
            raise ValidationException("Value required for this field.")
        return data

class Float(Field):
    name = 'float'
    _coerce = True

    def _deserialize(self, data):
        return float(data)

class HalfFloat(Float):
    name = 'half_float'

class ScaledFloat(Float):
    name = 'scaled_float'

    def __init__(self, scaling_factor, *args, **kwargs):
        super(ScaledFloat, self).__init__(scaling_factor=scaling_factor, *args, **kwargs)

class Double(Float):
    name = 'double'

class Integer(Field):
    name = 'integer'
    _coerce = True

    def _deserialize(self, data):
        return int(data)

class Byte(Integer):
    name = 'byte'

class Short(Integer):
    name = 'short'

class Long(Integer):
    name = 'long'

class Ip(Field):
    name = 'ip'
    _coerce = True

    def _deserialize(self, data):
        # the ipaddress library for pypy, python2.5 and 2.6 only accepts unicode.
        return ipaddress.ip_address(unicode(data))

    def _serialize(self, data):
        if data is None:
            return None
        return str(data)

class Binary(Field):
    name = 'binary'
    _coerce = True

    def clean(self, data):
        # Binary fields are opaque, so there's not much cleaning
        # that can be done.
        return data

    def _deserialize(self, data):
        return base64.b64decode(data)

    def _serialize(self, data):
        if data is None:
            return None
        return base64.b64encode(data).decode()

class GeoPoint(Field):
    name = 'geo_point'

class GeoShape(Field):
    name = 'geo_shape'

class Completion(Field):
    _param_defs = {
        'analyzer': {'type': 'analyzer'},
        'search_analyzer': {'type': 'analyzer'},
    }
    name = 'completion'

class Percolator(Field):
    name = 'percolator'
    _coerce = True

    def _deserialize(self, data):
        return Q(data)

    def _serialize(self, data):
        if data is None:
            return None
        return data.to_dict()

class RangeField(Field):
    _coerce = True

    def _deserialize(self, data):
        return Range(data)

    def _serialize(self, data):
        if data is None:
            return None
        if isinstance(data, collections_abc.Mapping):
            return data
        return data.to_dict()

class IntegerRange(RangeField):
    name = 'integer_range'

class FloatRange(RangeField):
    name = 'float_range'

class LongRange(RangeField):
    name = 'long_range'

class DoubleRange(RangeField):
    name = 'double_ranged'

class DateRange(RangeField):
    name = 'date_range'

class IpRange(Field):
    # not a RangeField since ip_range supports CIDR ranges
    name = 'ip_range'

class Join(Field):
    name = 'join'

class TokenCount(Field):
    name = 'token_count'

class Murmur3(Field):
    name = 'murmur3'
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import

# from django.core.urlresolvers import reverse
from django.db import models
from django.contrib.gis.db import models as geo_models
from polymorphic.models import PolymorphicModel
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe

from wastd.users.models import User

# Ancillary models -----------------------------------------------------------#
@python_2_unicode_compatible
class DistinguishingFeature(models.Model):
    """Distinguising Features.
    FEATURES_CHOICES = (
        ("damage-injury", "Obvious damage or injuries"),
        ("missing-limbs", "Missing limbs"),
        ("barnacles", "Barnacles"),
        ("algal-epiphytes-carapace", "Algal growth on carapace"),
        ("tagging-scars", "Tagging scars"),
        ("propeller-damage", "Propeller strike damage"),
        ("entanglement", "Entanglement in anthropogenic debris"),
        ("see-photo", "See attached photos"),)
    """
    name = models.CharField(
        max_length=300,
        verbose_name=_("Name"),
        help_text=_("A short name for the distinguising feature."),)

    description = models.TextField(
        verbose_name=_("Description"),
        blank=True, null=True,
        help_text=_("A description of the feature."),)

    class Meta:
        """Class options."""

        verbose_name = "Distinguishing Feature"
        verbose_name_plural = "Distinguishing Features"

    def __str__(self):
        """The unicode representation."""
        return self.name

# Observation models ---------------------------------------------------------#
@python_2_unicode_compatible
class Observation(PolymorphicModel, geo_models.Model):
    """The base Observation class knows when, where, who.

    When: Datetime of observation, stored in UTC, entered and displayed in local
    timezome.
    Where: Point in WGS84.
    Who: The observer has to be a registered system user.
    """

    when = models.DateTimeField(
        verbose_name=_("Observed on"),
        help_text=_("The observation datetime, shown here as local time, "
                    "stored as UTC."))
    where = geo_models.PointField(
        srid=4326,
        verbose_name=_("Observed at"),
        help_text=_("The observation location as point in WGS84"))
    who = models.ForeignKey(
        User,
        verbose_name=_("Observed by"),
        help_text=_("The observer has to be a registered system user"))

    class Meta:
        """Class options."""

        ordering = ["when", "where"]
        verbose_name = "Observation"
        verbose_name_plural = "Observations"
        get_latest_by = "when"

    def __str__(self):
        """The unicode representation."""
        return "Obs {0} on {1} by {2}".format(self.pk, self.when, self.who)

    @property
    def wkt(self):
        """Return the point coordinates as Well Known Text (WKT)."""
        return self.where.wkt

    @property
    def tag_html(self):
        """An HTML string of associated TagObservations"""
        return "".join(["
{0}
".format(t.__str__())
                        for t in self.tagobservation_set.all()])

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
Observation
{0}
{1} reported by {2}
".format(
                self.tag_html, self.when.strftime('%d/%m/%Y %H:%M:%S'), self.who))

@python_2_unicode_compatible
class StrandingObservation(Observation):
    """Opportunistic sighting of stranded/encountered dead or injured wildlife.

    Species use a local name list, but should lookup a webservice.
    This Observation is generic for all species. Other Models can FK this Model
    to add species-specific measurements.

    Observer name / address / phone / email is captured with observer as system
    user.

    The combination of species and health determines subsequent measurements
    and actions:

    * [turtle, dugong, cetacean] damage observation
    * [turtle, dugong, cetacean] distinguished features
    * [taxon] morphometrics
    * tag observation
    """

    HEALTH_CHOICES = (
        ('alive', 'Alive, healthy'),
        ('alive-injured', 'Alive, injured'),
        ('alive-then-died', 'Initally alive, but died'),
        ('dead-edible', 'Dead, carcass edible'),
        ('dead-organs-intact', 'Dead, decomposed but organs intact'),
        ('dead-advanced', 'Dead, advanced decomposition'),
        ('dead-mummified', 'Dead, skin holding bones'),
        ('dead-disarticulated', 'Dead, no soft tissue remaining'),
        ('other', 'Other'),)

    SPECIES_CHOICES = (
        ('Natator depressus', 'Flatback turtle (Natator depressus)'),
        ('Chelonia mydas', 'Green turtle (Chelonia mydas)'),
        ('Eretmochelys imbricata', 'Hawksbill turtle (Eretmochelys imbricata)'),
        ('Caretta caretta', 'Loggerhead turtle (Caretta caretta)'),
        ('Lepidochelys olivacea', 'Olive Ridley turtle (Lepidochelys olivacea)'),
        ('Dermochelys coriacea', 'Leatherback turtle (Dermochelys coriacea)'),
        ('unidentified', 'Unidentified'),)

    species = models.CharField(
        max_length=300,
        verbose_name=_("Species"),
        choices=SPECIES_CHOICES,
        help_text=_("The species of the animal."),)

    health = models.CharField(
        max_length=300,
        verbose_name=_("Health status"),
        choices=HEALTH_CHOICES,
        default="alive",
        help_text=_("On a scale from the Fresh Prince of Bel Air to 80s Hair "
                    "Metal: how dead and decomposed is the animal?"),)

    behaviour = models.TextField(
        verbose_name=_("Behaviour"),
        blank=True, null=True,
        help_text=_("Notes on condition or behaviour if alive."),)

    features = models.ManyToManyField(
        DistinguishingFeature,
        blank=True,
        help_text=_("Select any observed distinguishing features."),)

    management_actions = models.TextField(
        verbose_name=_("Management Actions"),
        blank=True, null=True,
        help_text=_("Managment actions taken. Keep updating as appropriate."),)

    comments = models.TextField(
        verbose_name=_("Comments"),
        blank=True, null=True,
        help_text=_("Any other comments or notes."),)

    def __str__(self):
        """The unicode representation."""
        return "StrandingObs {0} on {1} by {2} of {3}".format(
            self.pk, self.when, self.who, self.get_species_display())

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
{0}

{1}
{2}
seen on {3} reported by {4}
".format(
                self.get_species_display(), self.get_health_display(),
                self.tag_html, self.when.strftime('%d/%m/%Y %H:%M:%S'),
                self.who.name))

@python_2_unicode_compatible
class TurtleStrandingObservation(StrandingObservation):
    """Opportunistic sighting of stranded/encountered dead or injured turtle.

    Default stranding measurements, plus:

    * Sex
    * Maturity
    * Morphometrics
    """

    ACCURACY_CHOICES = (
        ("unknown", "Unknown"),
        ("estimated", "Estimated"),
        ("measured", "Measured"),)

    SEX_CHOICES = (
        ("male", "Male"),
        ("female", "Female"),
        ("unknown", "Unknown"),)

    MATURITY_CHOICES = (
        ("hatchling", "Hatchling"),
        ("juvenile", "Juvenile"),
        ("adult", "Adult"),
        ("unknown", "Unknown"),)

    sex = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Sex"),
        choices=SEX_CHOICES,
        help_text=_("The animal's sex."),)

    maturity = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Maturity"),
        choices=MATURITY_CHOICES,
        help_text=_("The animal's maturity."),)

    curved_carapace_length_mm = models.PositiveIntegerField(
        verbose_name=_("Curved Carapace Length (mm)"),
        blank=True, null=True,
        help_text=_("The Curved Carapace Length in millimetres."),)

    curved_carapace_length_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Curved Carapace Length Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    curved_carapace_width_mm = models.PositiveIntegerField(
        verbose_name=_("Curved Carapace Width (mm)"),
        blank=True, null=True,
        help_text=_("Curved Carapace Width in millimetres."),)

    curved_carapace_width_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Curved Carapace Width Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    tail_length_mm = models.PositiveIntegerField(
        verbose_name=_("c (mm)"),
        blank=True, null=True,
        help_text=_("The Tail Length, measured from carapace in millimetres."),)

    tail_length_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Tail Length Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    maximum_head_width_mm = models.PositiveIntegerField(
        verbose_name=_("Maximum Head Width (mm)"),
        blank=True, null=True,
        help_text=_("The Maximum Head Width in millimetres."),)

    maximum_head_width_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Maximum Head Width Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    def __str__(self):
        """The unicode representation."""
        return "TurtleStrandingObs {0} on {1} by {2} of {3}".format(
            self.pk, self.when, self.who, self.get_species_display())

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
{0}

{1} {4} {5}
{6}
seen on {2} reported by {3}
".format(
                self.get_species_display(), self.get_health_display(),
                self.when.strftime('%d/%m/%Y %H:%M:%S'), self.who,
                self.get_maturity_display(), self.get_sex_display(),
                self.tag_html))

# Child models of Observations -----------------------------------------------#
@python_2_unicode_compatible
class MediaAttachment(models.Model):
    """A media attachment to an Observation."""

    MEDIA_TYPE_CHOICES = (
        ('data_sheet', 'Original data sheet'),
        ('photograph', 'Photograph'),
        ('other', 'Other'),)

    observation = models.ForeignKey(
        Observation,
        verbose_name=_("Observation"),
        help_text=("Which Observation does this attachment relate to?"),)

    media_type = models.CharField(
        max_length=300,
        verbose_name=_("Attachment type"),
        choices=MEDIA_TYPE_CHOICES,
        default="other",
        help_text=_("What is the attached file about?"),)

    title = models.CharField(
        max_length=300,
        verbose_name=_("Attachment name"),
        blank=True, null=True,
        help_text=_("Give the attachment a representative name"),)

    attachment = models.FileField(
        upload_to='media/%Y/%m/%d/',
        verbose_name=_("File attachment"),
        help_text=_("Upload the file"),)

    def __str__(self):
        """The unicode representation."""
        return self.title

@python_2_unicode_compatible
class TagObservation(models.Model):
    """An Observation of an identifying tag on an observed entity.

    The identifying tag can be a flipper tag on a turtle, a PIT tag,
    a satellite tag, a barcode on a sample taken off an animal, a whisker ID
    from a picture of a pinniped, a genetic fingerprint or similar.

    The tag has its own life cycle through stages of production, delivery,
    affiliation with an animal, repeated sightings and disposal.

    The life cycle stages will vary between tag types.

    A TagObservation will find the tag in exactly one of the life cycle stages.

    The life history of each tag can be reconstructed from the sum of all of its
    TagObservations.

    As TagObservations can occur without an Observation of an animal, the
    FK to Observations is optional.
    """

    TYPE_CHOICES = (
        ('flipper-tag', 'Flipper Tag'),
        ('pit-tag', 'PIT Tag'),
        ('satellite-tag', 'Satellite Tag'),
        ('physical-sample', 'Physical Sample'),
        ('genetic-fingerprint', 'Genetic Fingerprint'),
        ('whisker-id', 'Whisker ID'),
        ('other', 'Other'),)

    STATUS_CHOICES = (
        ('ordered', 'Ordered from manufacturer'),
        ('produced', 'Produced by manufacturer'),
        ('delivered', 'Delivered to HQ'),
        ('allocated', 'Allocated to field team'),
        ('attached', 'Attached to an animal'),
        ('recaptured', 'Re-sighted as attached to animal'),
        ('detached', 'Taken off an animal'),
        ('found', 'Found detached'),
        ('returned', 'Returned to HQ'),
        ('decommissioned', 'Decommissioned from active tag pool'),
        ('destroyed', 'Destroyed'),
        ('observed', 'Observed in any other context, see comments'),)

    observation = models.ForeignKey(
        Observation,
        blank=True, null=True,
        verbose_name=_("Observation"),
        help_text=("During which Observation was this tag encountered?"),)

    type = models.CharField(
        max_length=300,
        verbose_name=_("Tag type"),
        choices=TYPE_CHOICES,
        default="flipper-tag",
        help_text=_("The Tag type."),)

    status = models.CharField(
        max_length=300,
        verbose_name=_("Tag status"),
        choices=STATUS_CHOICES,
        default="recaptured",
        help_text=_("The status this tag was seen in, or brought into."),)

    name = models.CharField(
        max_length=1000,
        verbose_name=_("Tag ID"),
        help_text=_("The ID of a tag must be unique within the tag type."),)

    comments = models.TextField(
        verbose_name=_("Comments"),
        blank=True, null=True,
        help_text=_("Any other comments or notes."),)

    def __str__(self):
        """The unicode representation."""
        return "{0} ({1})".format(self.name, self.get_status_display())
<commit_msg>labels of observations<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import

# from django.core.urlresolvers import reverse
from django.db import models
from django.contrib.gis.db import models as geo_models
from polymorphic.models import PolymorphicModel
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe

from wastd.users.models import User

# Ancillary models -----------------------------------------------------------#
@python_2_unicode_compatible
class DistinguishingFeature(models.Model):
    """Distinguising Features.
    FEATURES_CHOICES = (
        ("damage-injury", "Obvious damage or injuries"),
        ("missing-limbs", "Missing limbs"),
        ("barnacles", "Barnacles"),
        ("algal-epiphytes-carapace", "Algal growth on carapace"),
        ("tagging-scars", "Tagging scars"),
        ("propeller-damage", "Propeller strike damage"),
        ("entanglement", "Entanglement in anthropogenic debris"),
        ("see-photo", "See attached photos"),)
    """
    name = models.CharField(
        max_length=300,
        verbose_name=_("Name"),
        help_text=_("A short name for the distinguising feature."),)

    description = models.TextField(
        verbose_name=_("Description"),
        blank=True, null=True,
        help_text=_("A description of the feature."),)

    class Meta:
        """Class options."""

        verbose_name = "Distinguishing Feature"
        verbose_name_plural = "Distinguishing Features"

    def __str__(self):
        """The unicode representation."""
        return self.name

# Observation models ---------------------------------------------------------#
@python_2_unicode_compatible
class Observation(PolymorphicModel, geo_models.Model):
    """The base Observation class knows when, where, who.

    When: Datetime of observation, stored in UTC, entered and displayed in local
    timezome.
    Where: Point in WGS84.
    Who: The observer has to be a registered system user.
    """

    when = models.DateTimeField(
        verbose_name=_("Observed on"),
        help_text=_("The observation datetime, shown here as local time, "
                    "stored as UTC."))
    where = geo_models.PointField(
        srid=4326,
        verbose_name=_("Observed at"),
        help_text=_("The observation location as point in WGS84"))
    who = models.ForeignKey(
        User,
        verbose_name=_("Observed by"),
        help_text=_("The observer has to be a registered system user"))

    class Meta:
        """Class options."""

        ordering = ["when", "where"]
        verbose_name = "Observation"
        verbose_name_plural = "Observations"
        get_latest_by = "when"

    def __str__(self):
        """The unicode representation."""
        return "Obs {0} on {1} by {2}".format(self.pk, self.when, self.who)

    @property
    def wkt(self):
        """Return the point coordinates as Well Known Text (WKT)."""
        return self.where.wkt

    @property
    def tag_html(self):
        """An HTML string of associated TagObservations"""
        return "".join(["
{0}
".format(t.__str__())
                        for t in self.tagobservation_set.all()])

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
Observation
{0}
{1} reported by {2}
".format(
                self.tag_html, self.when.strftime('%d/%m/%Y %H:%M:%S'), self.who))

@python_2_unicode_compatible
class StrandingObservation(Observation):
    """Opportunistic sighting of stranded/encountered dead or injured wildlife.

    Species use a local name list, but should lookup a webservice.
    This Observation is generic for all species. Other Models can FK this Model
    to add species-specific measurements.

    Observer name / address / phone / email is captured with observer as system
    user.

    The combination of species and health determines subsequent measurements
    and actions:

    * [turtle, dugong, cetacean] damage observation
    * [turtle, dugong, cetacean] distinguished features
    * [taxon] morphometrics
    * tag observation
    """

    HEALTH_CHOICES = (
        ('alive', 'Alive (healthy)'),
        ('alive-injured', 'Alive (injured)'),
        ('alive-then-died', 'Initally alive (but died)'),
        ('dead-edible', 'Dead (carcass edible)'),
        ('dead-organs-intact', 'Dead (decomposed but organs intact)'),
        ('dead-advanced', 'Dead (advanced decomposition)'),
        ('dead-mummified', 'Mummified (dead, skin holding bones)'),
        ('dead-disarticulated', 'Disarticulated (dead, no soft tissue remaining)'),
        ('other', 'Other'),)

    SPECIES_CHOICES = (
        ('Natator depressus', 'Flatback turtle (Natator depressus)'),
        ('Chelonia mydas', 'Green turtle (Chelonia mydas)'),
        ('Eretmochelys imbricata', 'Hawksbill turtle (Eretmochelys imbricata)'),
        ('Caretta caretta', 'Loggerhead turtle (Caretta caretta)'),
        ('Lepidochelys olivacea', 'Olive Ridley turtle (Lepidochelys olivacea)'),
        ('Dermochelys coriacea', 'Leatherback turtle (Dermochelys coriacea)'),
        ('unidentified', 'Unidentified Species'),)

    species = models.CharField(
        max_length=300,
        verbose_name=_("Species"),
        choices=SPECIES_CHOICES,
        help_text=_("The species of the animal."),)

    health = models.CharField(
        max_length=300,
        verbose_name=_("Health status"),
        choices=HEALTH_CHOICES,
        default="alive",
        help_text=_("On a scale from the Fresh Prince of Bel Air to 80s Hair "
                    "Metal: how dead and decomposed is the animal?"),)

    behaviour = models.TextField(
        verbose_name=_("Behaviour"),
        blank=True, null=True,
        help_text=_("Notes on condition or behaviour if alive."),)

    features = models.ManyToManyField(
        DistinguishingFeature,
        blank=True,
        help_text=_("Select any observed distinguishing features."),)

    management_actions = models.TextField(
        verbose_name=_("Management Actions"),
        blank=True, null=True,
        help_text=_("Managment actions taken. Keep updating as appropriate."),)

    comments = models.TextField(
        verbose_name=_("Comments"),
        blank=True, null=True,
        help_text=_("Any other comments or notes."),)

    def __str__(self):
        """The unicode representation."""
        return "StrandingObs {0} on {1} by {2} of {3}".format(
            self.pk, self.when, self.who, self.get_species_display())

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
{0}

{1}
{2}
seen on {3} reported by {4}
".format(
                self.get_species_display(), self.get_health_display(),
                self.tag_html, self.when.strftime('%d/%m/%Y %H:%M:%S'),
                self.who.name))

@python_2_unicode_compatible
class TurtleStrandingObservation(StrandingObservation):
    """Opportunistic sighting of stranded/encountered dead or injured turtle.

    Default stranding measurements, plus:

    * Sex
    * Maturity
    * Morphometrics
    """

    ACCURACY_CHOICES = (
        ("unknown", "Unknown"),
        ("estimated", "Estimated"),
        ("measured", "Measured"),)

    SEX_CHOICES = (
        ("male", "Male"),
        ("female", "Female"),
        ("unknown", "Unknown Sex"),)

    MATURITY_CHOICES = (
        ("hatchling", "Hatchling"),
        ("juvenile", "Juvenile"),
        ("adult", "Adult"),
        ("unknown", "Unknown Maturity"),)

    sex = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Sex"),
        choices=SEX_CHOICES,
        help_text=_("The animal's sex."),)

    maturity = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Maturity"),
        choices=MATURITY_CHOICES,
        help_text=_("The animal's maturity."),)

    curved_carapace_length_mm = models.PositiveIntegerField(
        verbose_name=_("Curved Carapace Length (mm)"),
        blank=True, null=True,
        help_text=_("The Curved Carapace Length in millimetres."),)

    curved_carapace_length_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Curved Carapace Length Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    curved_carapace_width_mm = models.PositiveIntegerField(
        verbose_name=_("Curved Carapace Width (mm)"),
        blank=True, null=True,
        help_text=_("Curved Carapace Width in millimetres."),)

    curved_carapace_width_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Curved Carapace Width Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    tail_length_mm = models.PositiveIntegerField(
        verbose_name=_("Tail Length (mm)"),
        blank=True, null=True,
        help_text=_("The Tail Length, measured from carapace in millimetres."),)

    tail_length_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Tail Length Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    maximum_head_width_mm = models.PositiveIntegerField(
        verbose_name=_("Maximum Head Width (mm)"),
        blank=True, null=True,
        help_text=_("The Maximum Head Width in millimetres."),)

    maximum_head_width_accuracy = models.CharField(
        max_length=300,
        default="unknown",
        verbose_name=_("Maximum Head Width Accuracy"),
        choices=ACCURACY_CHOICES,
        help_text=_("The measurement type as indication of accuracy."),)

    def __str__(self):
        """The unicode representation."""
        return "TurtleStrandingObs {0} on {1} by {2} of {3}".format(
            self.pk, self.when, self.who, self.get_species_display())

    @property
    def popupContent(self):
        """HTML for a map popup."""
        return mark_safe(
            "
{0}

{1} {4} {5}
{6}
seen on {2} reported by {3}
".format(
                self.get_species_display(), self.get_health_display(),
                self.when.strftime('%d/%m/%Y %H:%M:%S'), self.who,
                self.get_maturity_display(), self.get_sex_display(),
                self.tag_html))

# Child models of Observations -----------------------------------------------#
@python_2_unicode_compatible
class MediaAttachment(models.Model):
    """A media attachment to an Observation."""

    MEDIA_TYPE_CHOICES = (
        ('data_sheet', 'Original data sheet'),
        ('photograph', 'Photograph'),
        ('other', 'Other'),)

    observation = models.ForeignKey(
        Observation,
        verbose_name=_("Observation"),
        help_text=("Which Observation does this attachment relate to?"),)

    media_type = models.CharField(
        max_length=300,
        verbose_name=_("Attachment type"),
        choices=MEDIA_TYPE_CHOICES,
        default="other",
        help_text=_("What is the attached file about?"),)

    title = models.CharField(
        max_length=300,
        verbose_name=_("Attachment name"),
        blank=True, null=True,
        help_text=_("Give the attachment a representative name"),)

    attachment = models.FileField(
        upload_to='media/%Y/%m/%d/',
        verbose_name=_("File attachment"),
        help_text=_("Upload the file"),)

    def __str__(self):
        """The unicode representation."""
        return self.title

@python_2_unicode_compatible
class TagObservation(models.Model):
    """An Observation of an identifying tag on an observed entity.

    The identifying tag can be a flipper tag on a turtle, a PIT tag,
    a satellite tag, a barcode on a sample taken off an animal, a whisker ID
    from a picture of a pinniped, a genetic fingerprint or similar.

    The tag has its own life cycle through stages of production, delivery,
    affiliation with an animal, repeated sightings and disposal.

    The life cycle stages will vary between tag types.

    A TagObservation will find the tag in exactly one of the life cycle stages.

    The life history of each tag can be reconstructed from the sum of all of its
    TagObservations.

    As TagObservations can occur without an Observation of an animal, the
    FK to Observations is optional.
    """

    TYPE_CHOICES = (
        ('flipper-tag', 'Flipper Tag'),
        ('pit-tag', 'PIT Tag'),
        ('satellite-tag', 'Satellite Tag'),
        ('physical-sample', 'Physical Sample'),
        ('genetic-fingerprint', 'Genetic Fingerprint'),
        ('whisker-id', 'Whisker ID'),
        ('other', 'Other'),)

    STATUS_CHOICES = (
        ('ordered', 'Ordered from manufacturer'),
        ('produced', 'Produced by manufacturer'),
        ('delivered', 'Delivered to HQ'),
        ('allocated', 'Allocated to field team'),
        ('attached', 'Attached to an animal'),
        ('recaptured', 'Re-sighted as attached to animal'),
        ('detached', 'Taken off an animal'),
        ('found', 'Found detached'),
        ('returned', 'Returned to HQ'),
        ('decommissioned', 'Decommissioned from active tag pool'),
        ('destroyed', 'Destroyed'),
        ('observed', 'Observed in any other context, see comments'),)

    observation = models.ForeignKey(
        Observation,
        blank=True, null=True,
        verbose_name=_("Observation"),
        help_text=("During which Observation was this tag encountered?"),)

    type = models.CharField(
        max_length=300,
        verbose_name=_("Tag type"),
        choices=TYPE_CHOICES,
        default="flipper-tag",
        help_text=_("The Tag type."),)

    status = models.CharField(
        max_length=300,
        verbose_name=_("Tag status"),
        choices=STATUS_CHOICES,
        default="recaptured",
        help_text=_("The status this tag was seen in, or brought into."),)

    name = models.CharField(
        max_length=1000,
        verbose_name=_("Tag ID"),
        help_text=_("The ID of a tag must be unique within the tag type."),)

    comments = models.TextField(
        verbose_name=_("Comments"),
        blank=True, null=True,
        help_text=_("Any other comments or notes."),)

    def __str__(self):
        """The unicode representation."""
        return "{0} ({1})".format(self.name, self.get_status_display())
<|endoftext|>


<commit_before>from django.contrib.contenttypes.generic import GenericInlineModelAdmin

from ella.ellacomments.models import CommentOptionsObject

class CommentOptionsGenericInline(GenericInlineModelAdmin):
    model = CommentOptionsObject

    max_num = 1<commit_msg>ella.ellacomments.admin.CommentsAdmin is replacement of zenaadmin.models.CommentsOptions<commit_after>from django.contrib.contenttypes.generic import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_unicode

from ella.ellacomments.models import CommentOptionsObject
from threadedcomments.admin import ThreadedCommentsAdmin
from threadedcomments.models import ThreadedComment

class CommentOptionsGenericInline(GenericInlineModelAdmin):
    model = CommentOptionsObject
    max_num = 1

class CommentsAdmin(ThreadedCommentsAdmin):
    def __call__( self, request, url ):
        import sys
        if url and url.startswith( 'deleterelated' ):
            return self.delete_related( request, *url.split('/')[1:] )
        if url and url.startswith( 'info' ):
            return self.json_info( request, *url.split('/')[1:] )
        return super( CommentsAdmin, self ).__call__( request, url )

    def delete_related(self, request, ct, id):
        comments = ThreadedComment.objects.filter(content_type=ct, object_pk=id)
        ct = ContentType.objects.get(pk=ct)
        model = ct.model_class()
        obj = model.objects.get(pk=id)
        comment_count = comments.count()
        obj_url = "%s%s/%s/%s" % (self.admin_site.root_path, ct.app_label, ct.model, id)

        if not self.has_delete_permission(request):
            raise PermissionDenied

        if request.POST: # The user has already confirmed the deletion.
            for comment in comments:
                try:
                    comment.delete()
                except ObjectDoesNotExist, e:
                    pass

            from django.contrib.admin.models import LogEntry, CHANGE
            LogEntry.objects.log_action(request.user.id, ct.id, id, force_unicode(obj),
                                        CHANGE, force_unicode(_('Comments deleted')))

            request.user.message_set.create(message=_('The comments were deleted successfully.').__unicode__())
            return HttpResponseRedirect(obj_url)

        context = {
            "title": _("Are you sure?"),
            "ct": ct,
            "model_name": model._meta.verbose_name,
            "obj": obj,
            "app_label": model._meta.app_label,
            'root_path': self.admin_site.root_path,
            "comment_count": comment_count,
            "no_comments": comment_count == 0,
        }
        return render_to_response("admin/comments/comment/multiple_delete_confirmation.html", context)
<|endoftext|>