use sqlalchemy_schemadisplay module
authorSandro Knauß <bugs@sandroknauss.de>
Tue, 15 Apr 2025 01:20:56 +0200
changeset 313 a88add2b3eea
parent 312 42fd5075a5d1
child 314 353541614dfa
use sqlalchemy_schemadisplay module
createerm.py
sqlalchemy_schemadisplay3.py
web/images/db-schema.svg
--- a/createerm.py	Tue Apr 15 01:19:47 2025 +0200
+++ b/createerm.py	Tue Apr 15 01:20:56 2025 +0200
@@ -36,8 +36,10 @@
 
 #schema plot
 def createSchemaPlot(fname):
-    from sqlalchemy_schemadisplay3 import create_schema_graph
-    graph = create_schema_graph(metadata=schema.Base.metadata,
+    from sqlalchemy_schemadisplay import create_schema_graph
+    graph = create_schema_graph(engine=None,
+        metadata=schema.Base.metadata,
+        tables=tables,
         show_datatypes=True, # The image too large if datatypes shown
         show_indexes=True, # ditto for indexes
         rankdir='LR', # From left to right (instead of top to bottom)
@@ -47,25 +49,3 @@
     graph.set_size('6.5,10')
     #graph.set_ratio("fill")
     graph.write_svg(fname)
-
-#umlplot
-def createUMLPlot(fname):
-    from sqlalchemy_schemadisplay3 import create_uml_graph
-    from sqlalchemy.orm import class_mapper
-    mappers = []
-    for attr in dir(schema.model):
-        if attr[0] == '_': continue
-        try:
-            cls = getattr(schema.model, attr)
-            mappers.append(class_mapper(cls))
-        except:
-            pass
-    #pass them to the function and set some formatting options
-    graph = create_uml_graph(mappers,
-        show_operations=False, # not necessary in this case
-        show_multiplicity_one=True, # some people like to see the ones
-        show_attributes=True,
-    )
-    graph.set_size('6,5')
-    graph.set_ratio("fill")
-    graph.write_png('test.png')
--- a/sqlalchemy_schemadisplay3.py	Tue Apr 15 01:19:47 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
-# 
-# This file is part of Iro.
-# 
-# Permission is hereby granted, free of charge, to any person obtaining a copy of
-# this software and associated documentation files (the "Software"), to deal in
-# the Software without restriction, including without limitation the rights to use,
-# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
-# #Software, and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-# 
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-# 
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-# updated SQLA schema display to work with pydot 1.0.2
-# download from: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay
-
-
-from sqlalchemy.orm.properties import PropertyLoader
-import pydot
-import types
-
-__all__ = ['create_uml_graph', 'create_schema_graph', 'show_uml_graph', 'show_schema_graph']
-
-def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize):
-    html = '<<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="%d" BALIGN="LEFT"><TR><TD><FONT POINT-SIZE="10">%s</FONT></TD></TR>' % (bordersize, mapper.class_.__name__)
-    def format_col(col):
-        colstr = '+%s' % (col.name)
-        if show_datatypes:
-            colstr += ' : %s' % (col.type.__class__.__name__)
-        return colstr
-            
-    if show_attributes:
-        html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key))
-    else:
-        [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)]
-    if show_operations:
-        html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(
-            '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in 
-                zip((func.func_defaults and len(func.func_code.co_varnames)-1-(len(func.func_defaults) or 0) or func.func_code.co_argcount-1)*[_mk_label]+list(func.func_defaults or []), func.func_code.co_varnames[1:])
-            ))
-            for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__
-        )
-    html+= '</TABLE>>'
-    return html
-
-
-def create_uml_graph(mappers, show_operations=True, show_attributes=True, show_multiplicity_one=False, show_datatypes=True, linewidth=1.0, font="Bitstream-Vera Sans"):
-    graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75")
-    relations = set()
-    for mapper in mappers:
-        graph.add_node(pydot.Node(mapper.class_.__name__,
-            shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth),
-            fontname=font, fontsize="8.0",
-        ))
-        if mapper.inherits:
-            graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__,
-                arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth)))
-        for loader in mapper.iterate_properties:
-            if isinstance(loader, PropertyLoader) and loader.mapper in mappers:
-                if hasattr(loader, 'reverse_property'):
-                    relations.add(frozenset([loader, loader.reverse_property]))
-                else:
-                    relations.add(frozenset([loader]))
-
-    for relation in relations:
-        #if len(loaders) > 2:
-        #    raise Exception("Warning: too many loaders for join %s" % join)
-        args = {}
-        def multiplicity_indicator(prop):
-            if prop.uselist:
-                return ' *'
-            if any(col.nullable for col in prop.local_side):
-                return ' 0..1'
-            if show_multiplicity_one:
-                return ' 1'
-            return ''
-        
-        if len(relation) == 2:
-            src, dest = relation
-            from_name = src.parent.class_.__name__
-            to_name = dest.parent.class_.__name__
-            
-            def calc_label(src,dest):
-                return '+' + src.key + multiplicity_indicator(src)
-            args['headlabel'] = calc_label(src,dest)
-            
-            args['taillabel'] = calc_label(dest,src)
-            args['arrowtail'] = 'none'
-            args['arrowhead'] = 'none'
-            args['constraint'] = False
-        else:
-            prop, = relation
-            from_name = prop.parent.class_.__name__
-            to_name = prop.mapper.class_.__name__
-            args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop))
-            args['arrowtail'] = 'none'
-            args['arrowhead'] = 'vee'
-        
-        graph.add_edge(pydot.Edge(from_name,to_name,
-            fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth),
-            **args)
-        )
-
-    return graph
-
-#from sqlalchemy import Table, text
-
-def _render_table_html(table, metadata, show_indexes, show_datatypes):
-    def format_col_type(col):
-        try:
-            return col.type.get_col_spec()
-        except NotImplementedError:
-            return str(col.type)
-        except AttributeError:
-            return str(col.type)
-    def format_col_str(col):
-         if show_datatypes:
-             return "- %s : %s" % (col.name, format_col_type(col))
-         else:
-             return "- %s" % col.name
-    html = '<<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">%s</TD></TR><TR><TD BORDER="1" CELLPADDING="0"></TD></TR>' % table.name 
-
-    html += ''.join('<TR><TD ALIGN="LEFT" PORT="%s">%s</TD></TR>' % (col.name, format_col_str(col)) for col in table.columns)
-    html += '</TABLE>>'
-    return html
-
-def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans",
-    concentrate=True, relation_options={}, rankdir='TB'):
-    relation_kwargs = {
-        'fontsize':"7.0"
-    }
-    relation_kwargs.update(relation_options)
-    
-    if not metadata and len(tables):
-        metadata = tables[0].metadata
-    elif not tables and metadata:
-        if not len(metadata.tables):
-            metadata.reflect()
-        tables = metadata.tables.values()
-    else:
-        raise Exception("You need to specify at least tables or metadata")
-    
-    graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir)
-    for table in tables:
-        graph.add_node(pydot.Node(str(table.name),
-            shape="plaintext",
-            label=_render_table_html(table, metadata, show_indexes, show_datatypes),
-            fontname=font, fontsize="7.0"
-        ))
-    
-    for table in tables:
-        for fk in table.foreign_keys:
-            edge = [table.name, fk.column.table.name]
-            is_inheritance = fk.parent.primary_key and fk.column.primary_key
-            if is_inheritance:
-                edge = edge[::-1]
-            graph_edge = pydot.Edge(
-                headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name,
-                arrowhead=is_inheritance and 'none' or 'odot' ,
-                arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' ,
-                fontname=font, 
-                #samehead=fk.column.name, sametail=fk.parent.name,
-                *edge, **relation_kwargs
-            )
-            graph.add_edge(graph_edge)
-
-# not sure what this part is for, doesn't work with pydot 1.0.2
-#            graph_edge.parent_graph = graph.parent_graph
-#            if table.name not in [e.get_source() for e in graph.get_edge_list()]:
-#                graph.edge_src_list.append(table.name)
-#            if fk.column.table.name not in graph.edge_dst_list:
-#                graph.edge_dst_list.append(fk.column.table.name)
-#            graph.sorted_graph_elements.append(graph_edge)
-    return graph
-
-def show_uml_graph(*args, **kwargs):
-    from cStringIO import StringIO
-    from PIL import Image
-    iostream = StringIO(create_uml_graph(*args, **kwargs).create_png())
-    Image.open(iostream).show(command=kwargs.get('command','gwenview'))
-
-def show_schema_graph(*args, **kwargs):
-    from cStringIO import StringIO
-    from PIL import Image
-    iostream = StringIO(create_schema_graph(*args, **kwargs).create_png())
-    Image.open(iostream).show(command=kwargs.get('command','gwenview'))
--- a/web/images/db-schema.svg	Tue Apr 15 01:19:47 2025 +0200
+++ b/web/images/db-schema.svg	Tue Apr 15 01:20:56 2025 +0200
@@ -1,99 +1,128 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.26.3 (20100126.1600)
+<!-- Generated by graphviz version 2.42.4 (0)
  -->
 <!-- Title: G Pages: 1 -->
-<svg width="468pt" height="126pt"
- viewBox="0.00 0.00 468.00 125.74" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph1" class="graph" transform="scale(0.83274 0.83274) rotate(0) translate(4 147)">
+<svg width="468pt" height="246pt"
+ viewBox="0.00 0.00 468.00 245.75" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(0.9 0.9) rotate(0) translate(4 268)">
 <title>G</title>
-<polygon fill="white" stroke="white" points="-4,5 -4,-147 559,-147 559,5 -4,5"/>
-<!-- message -->
-<g id="node1" class="node"><title>message</title>
-<polygon fill="none" stroke="black" points="8,-18.5 8,-133.5 112,-133.5 112,-18.5 8,-18.5"/>
-<text text-anchor="start" x="43.5" y="-124.367" font-family="Bitstream-Vera Sans" font-size="7.00">message</text>
-<polygon fill="none" stroke="black" points="9,-118 9,-120 111,-120 111,-118 9,-118"/>
-<text text-anchor="start" x="11" y="-110.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
-<text text-anchor="start" x="11" y="-99.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; recipient : VARCHAR(100)</text>
-<text text-anchor="start" x="11" y="-88.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; isBilled : BOOLEAN</text>
-<text text-anchor="start" x="11" y="-77.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; date : DATETIME</text>
-<text text-anchor="start" x="11" y="-66.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; price : NUMERIC(8, 4)</text>
-<text text-anchor="start" x="11" y="-55.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; count : INTEGER</text>
-<text text-anchor="start" x="11" y="-44.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; exID : VARCHAR(100)</text>
-<text text-anchor="start" x="11" y="-33.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; job : VARCHAR(40)</text>
-<text text-anchor="start" x="11" y="-22.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-268 514,-268 514,4 -4,4"/>
+<!-- apiuser -->
+<g id="node1" class="node">
+<title>apiuser</title>
+<text text-anchor="start" x="313" y="-47.4" font-family="Bitstream-Vera Sans" font-size="7.00">apiuser</text>
+<polygon fill="none" stroke="black" points="285.5,-41 285.5,-43 365.5,-43 365.5,-41 285.5,-41"/>
+<text text-anchor="start" x="287.5" y="-33.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
+<text text-anchor="start" x="287.5" y="-21.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; ng_kunde : INTEGER</text>
+<text text-anchor="start" x="287.5" y="-9.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; apikey : VARCHAR(50)</text>
+<polygon fill="none" stroke="black" points="284.5,-4 284.5,-56 366.5,-56 366.5,-4 284.5,-4"/>
 </g>
-<!-- job -->
-<g id="node3" class="node"><title>job</title>
-<polygon fill="none" stroke="black" points="164.5,-79 164.5,-139 251.5,-139 251.5,-79 164.5,-79"/>
-<text text-anchor="start" x="202.5" y="-130.367" font-family="Bitstream-Vera Sans" font-size="7.00">job</text>
-<polygon fill="none" stroke="black" points="166,-124 166,-126 251,-126 251,-124 166,-124"/>
-<text text-anchor="start" x="168" y="-116.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
-<text text-anchor="start" x="168" y="-105.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; info : VARCHAR(100)</text>
-<text text-anchor="start" x="168" y="-94.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; status : VARCHAR(7)</text>
-<text text-anchor="start" x="168" y="-83.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
+<!-- userright -->
+<g id="node5" class="node">
+<title>userright</title>
+<text text-anchor="start" x="447.5" y="-89.4" font-family="Bitstream-Vera Sans" font-size="7.00">userright</text>
+<polygon fill="none" stroke="black" points="424.5,-83 424.5,-85 501.5,-85 501.5,-83 424.5,-83"/>
+<text text-anchor="start" x="426.5" y="-75.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
+<text text-anchor="start" x="426.5" y="-63.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
+<text text-anchor="start" x="426.5" y="-51.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; default : INTEGER</text>
+<polygon fill="none" stroke="black" points="423,-46 423,-98 502,-98 502,-46 423,-46"/>
 </g>
-<!-- message&#45;&gt;job -->
-<g id="edge2" class="edge"><title>message&#45;&gt;job</title>
-<path fill="none" stroke="black" d="M120.197,-89.4224C129.482,-91.4926 139.067,-93.6298 148.34,-95.6974"/>
-<ellipse fill="none" stroke="black" cx="152.488" cy="-96.6224" rx="4.00001" ry="4.00001"/>
-<text text-anchor="middle" x="146.627" y="-97.3455" font-family="Bitstream-Vera Sans" font-size="7.00">+ id</text>
-<text text-anchor="middle" x="129.963" y="-84.9699" font-family="Bitstream-Vera Sans" font-size="7.00">+ job</text>
+<!-- apiuser&#45;&gt;userright -->
+<g id="edge2" class="edge">
+<title>apiuser&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M384.46,-48.03C394.61,-51.19 405.05,-54.44 414.84,-57.48"/>
+<polygon fill="none" stroke="black" points="385.45,-44.67 374.86,-45.04 383.37,-51.36 385.45,-44.67"/>
+<text text-anchor="middle" x="402.34" y="-51.88" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="384.86" y="-39.44" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
 </g>
 <!-- offer -->
-<g id="node5" class="node"><title>offer</title>
-<polygon fill="none" stroke="black" points="304.5,-4 304.5,-64 405.5,-64 405.5,-4 304.5,-4"/>
-<text text-anchor="start" x="347" y="-55.3667" font-family="Bitstream-Vera Sans" font-size="7.00">offer</text>
-<polygon fill="none" stroke="black" points="306,-49 306,-51 405,-51 405,-49 306,-49"/>
-<text text-anchor="start" x="308" y="-41.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
-<text text-anchor="start" x="308" y="-30.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; provider : VARCHAR(100)</text>
-<text text-anchor="start" x="308" y="-19.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; route : VARCHAR(100)</text>
-<text text-anchor="start" x="308" y="-8.86667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; typ : VARCHAR(100)</text>
+<g id="node2" class="node">
+<title>offer</title>
+<text text-anchor="start" x="318" y="-137.4" font-family="Bitstream-Vera Sans" font-size="7.00">offer</text>
+<polygon fill="none" stroke="black" points="281.5,-131 281.5,-133 370.5,-133 370.5,-131 281.5,-131"/>
+<text text-anchor="start" x="283.5" y="-123.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
+<text text-anchor="start" x="283.5" y="-111.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; provider : VARCHAR(100)</text>
+<text text-anchor="start" x="283.5" y="-99.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; route : VARCHAR(100)</text>
+<text text-anchor="start" x="283.5" y="-87.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; typ : VARCHAR(100)</text>
+<polygon fill="none" stroke="black" points="280,-82 280,-146 371,-146 371,-82 280,-82"/>
+</g>
+<!-- offer&#45;&gt;userright -->
+<g id="edge3" class="edge">
+<title>offer&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M388.91,-94.58C397.69,-91.85 406.58,-89.09 414.99,-86.47"/>
+<polygon fill="none" stroke="black" points="387.59,-91.33 379.08,-97.64 389.67,-98.01 387.59,-91.33"/>
+<text text-anchor="middle" x="402.49" y="-80.87" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="390.08" y="-92.04" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+<!-- status -->
+<g id="node3" class="node">
+<title>status</title>
+<text text-anchor="start" x="44.5" y="-251.4" font-family="Bitstream-Vera Sans" font-size="7.00">status</text>
+<polygon fill="none" stroke="black" points="9.5,-245 9.5,-247 98.5,-247 98.5,-245 9.5,-245"/>
+<text text-anchor="start" x="11.5" y="-237.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="11.5" y="-225.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; date : DATETIME</text>
+<text text-anchor="start" x="11.5" y="-213.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; recipient : VARCHAR(100)</text>
+<text text-anchor="start" x="11.5" y="-201.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; exID : VARCHAR(100)</text>
+<text text-anchor="start" x="11.5" y="-189.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; status : INTEGER</text>
+<text text-anchor="start" x="11.5" y="-177.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; sender : VARCHAR(100)</text>
+<text text-anchor="start" x="11.5" y="-165.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; data : VARCHAR(2550)</text>
+<polygon fill="none" stroke="black" points="8,-160 8,-260 99,-260 99,-160 8,-160"/>
+</g>
+<!-- job -->
+<g id="node4" class="node">
+<title>job</title>
+<text text-anchor="start" x="184.5" y="-63.4" font-family="Bitstream-Vera Sans" font-size="7.00">job</text>
+<polygon fill="none" stroke="black" points="152.5,-57 152.5,-59 227.5,-59 227.5,-57 152.5,-57"/>
+<text text-anchor="start" x="154.5" y="-49.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="154.5" y="-37.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; info : VARCHAR(100)</text>
+<text text-anchor="start" x="154.5" y="-25.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; status : VARCHAR(7)</text>
+<text text-anchor="start" x="154.5" y="-13.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
+<polygon fill="none" stroke="black" points="151,-8 151,-72 228,-72 228,-8 151,-8"/>
+</g>
+<!-- job&#45;&gt;apiuser -->
+<g id="edge1" class="edge">
+<title>job&#45;&gt;apiuser</title>
+<path fill="none" stroke="black" d="M246.41,-35.83C253.52,-35.3 260.82,-34.75 267.95,-34.22"/>
+<polygon fill="black" stroke="black" points="246.21,-35.84 235.91,-32.1 241.23,-36.21 236.24,-36.59 236.24,-36.59 236.24,-36.59 241.23,-36.21 236.58,-41.07 246.21,-35.84 246.21,-35.84"/>
+<ellipse fill="none" stroke="black" cx="272.23" cy="-33.9" rx="4" ry="4"/>
+<text text-anchor="middle" x="263.72" y="-36" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="246.24" y="-30.99" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- message -->
+<g id="node6" class="node">
+<title>message</title>
+<text text-anchor="start" x="39.5" y="-125.4" font-family="Bitstream-Vera Sans" font-size="7.00">message</text>
+<polygon fill="none" stroke="black" points="9.5,-119 9.5,-121 98.5,-121 98.5,-119 9.5,-119"/>
+<text text-anchor="start" x="11.5" y="-111.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="11.5" y="-99.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; recipient : VARCHAR(100)</text>
+<text text-anchor="start" x="11.5" y="-87.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; isBilled : BOOLEAN</text>
+<text text-anchor="start" x="11.5" y="-75.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; date : DATETIME</text>
+<text text-anchor="start" x="11.5" y="-63.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; price : NUMERIC(8, 4)</text>
+<text text-anchor="start" x="11.5" y="-51.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; count : INTEGER</text>
+<text text-anchor="start" x="11.5" y="-39.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; exID : VARCHAR(100)</text>
+<text text-anchor="start" x="11.5" y="-27.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; job : VARCHAR(40)</text>
+<text text-anchor="start" x="11.5" y="-15.4" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
+<polygon fill="none" stroke="black" points="8,-10 8,-134 99,-134 99,-10 8,-10"/>
 </g>
 <!-- message&#45;&gt;offer -->
-<g id="edge4" class="edge"><title>message&#45;&gt;offer</title>
-<path fill="none" stroke="black" d="M120.053,-67.45C168.906,-60.4948 237.788,-50.6879 288.339,-43.4907"/>
-<ellipse fill="none" stroke="black" cx="292.329" cy="-42.9227" rx="4.00001" ry="4.00001"/>
-<text text-anchor="middle" x="287.912" y="-45.5203" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
-<text text-anchor="middle" x="128.43" y="-59.6886" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
-</g>
-<!-- userright -->
-<g id="node2" class="node"><title>userright</title>
-<polygon fill="none" stroke="black" points="458,-49.5 458,-98.5 546,-98.5 546,-49.5 458,-49.5"/>
-<text text-anchor="start" x="485" y="-89.3667" font-family="Bitstream-Vera Sans" font-size="7.00">userright</text>
-<polygon fill="none" stroke="black" points="459,-83 459,-85 545,-85 545,-83 459,-83"/>
-<text text-anchor="start" x="461" y="-75.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
-<text text-anchor="start" x="461" y="-64.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
-<text text-anchor="start" x="461" y="-53.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; default : INTEGER</text>
+<g id="edge4" class="edge">
+<title>message&#45;&gt;offer</title>
+<path fill="none" stroke="black" d="M117.25,-81.76C161.44,-88.64 220.27,-97.79 263.96,-104.58"/>
+<polygon fill="black" stroke="black" points="117.08,-81.73 107.89,-75.75 112.14,-80.97 107.2,-80.2 107.2,-80.2 107.2,-80.2 112.14,-80.97 106.51,-84.64 117.08,-81.73 117.08,-81.73"/>
+<ellipse fill="none" stroke="black" cx="267.98" cy="-105.21" rx="4" ry="4"/>
+<text text-anchor="middle" x="259.43" y="-100.22" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="118.2" y="-74.6" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
 </g>
-<!-- apiuser -->
-<g id="node4" class="node"><title>apiuser</title>
-<polygon fill="none" stroke="black" points="309,-89.5 309,-138.5 401,-138.5 401,-89.5 309,-89.5"/>
-<text text-anchor="start" x="341" y="-129.367" font-family="Bitstream-Vera Sans" font-size="7.00">apiuser</text>
-<polygon fill="none" stroke="black" points="310,-123 310,-125 400,-125 400,-123 310,-123"/>
-<text text-anchor="start" x="312" y="-115.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
-<text text-anchor="start" x="312" y="-104.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; ng_kunde : INTEGER</text>
-<text text-anchor="start" x="312" y="-93.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; apikey : VARCHAR(50)</text>
-</g>
-<!-- job&#45;&gt;apiuser -->
-<g id="edge10" class="edge"><title>job&#45;&gt;apiuser</title>
-<path fill="none" stroke="black" d="M259.827,-110.763C270.366,-111.121 281.558,-111.502 292.424,-111.872"/>
-<ellipse fill="none" stroke="black" cx="296.657" cy="-112.015" rx="4" ry="4"/>
-<text text-anchor="middle" x="291.453" y="-113.767" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
-<text text-anchor="middle" x="269.028" y="-104.547" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
-</g>
-<!-- apiuser&#45;&gt;userright -->
-<g id="edge8" class="edge"><title>apiuser&#45;&gt;userright</title>
-<path fill="none" stroke="black" d="M409.32,-99.2191C422.569,-95.6138 436.713,-91.7653 449.858,-88.1883"/>
-<text text-anchor="middle" x="442.223" y="-92.3458" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
-<text text-anchor="middle" x="416.955" y="-90.4616" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
-</g>
-<!-- offer&#45;&gt;userright -->
-<g id="edge6" class="edge"><title>offer&#45;&gt;userright</title>
-<path fill="none" stroke="black" d="M413.521,-49.924C425.531,-53.192 438.098,-56.6116 449.865,-59.8135"/>
-<text text-anchor="middle" x="440.01" y="-59.2118" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
-<text text-anchor="middle" x="423.375" y="-45.9257" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+<!-- message&#45;&gt;job -->
+<g id="edge5" class="edge">
+<title>message&#45;&gt;job</title>
+<path fill="none" stroke="black" d="M116.81,-57.12C122.78,-55.69 128.8,-54.26 134.68,-52.85"/>
+<polygon fill="black" stroke="black" points="116.8,-57.12 106.03,-55.07 111.94,-58.28 107.08,-59.44 107.08,-59.44 107.08,-59.44 111.94,-58.28 108.12,-63.82 116.8,-57.12 116.8,-57.12"/>
+<ellipse fill="none" stroke="black" cx="138.71" cy="-51.89" rx="4" ry="4"/>
+<text text-anchor="middle" x="136.6" y="-45.36" font-family="Bitstream-Vera Sans" font-size="7.00">+ id</text>
+<text text-anchor="middle" x="115.08" y="-53.84" font-family="Bitstream-Vera Sans" font-size="7.00">+ job</text>
 </g>
 </g>
 </svg>