Data Model#
Resource Model Overview#
Resources in an Arches database are separated into distinct Resource Models designed to represent a kind of physical real-world resource, such as a historic artifact or event. In the technical sense, the term Resource Model refers collectively to the following user-facing elements in Arches:
A Graph data structure representing a physical real-world resource, such as a building, a public figure, a website, an archaeological site, or a historic document.
A set of Cards to collect and display data associated with instances of this Resource Model.
The relationships among these components and their dependencies are visualized below:
The Arches logical model has been developed to support this modular construction, and the relevant models are described below as they pertain to the graph, UI components, and the resource data itself (not illustrated above).
Note
In the UI you will see a distinction between “Resource Models” and
“Branches”, but underneath these are both made from instances of the Graph model. The primary difference between the two
is the isresource
property, which is set to True
for a
Resource Model.
Branches are used for records that might appear in multiple Resource Models, such as a person or place. Branches can be included as children of any Ontology-permitted Node in a Resource Model.
Controllers#
Arches platform code defines base classes for some of its core data models, and uses proxy models to implement their controllers. In smaller classes, “controller” code is included with the data model class. This documentation primarily discusses the models, but controller behavior is discussed where relevant to how the models are used, and all models are referred to by their more succinct “controller” name.
Model |
Controller |
Note
ResourceInstance
breaks the implicit naming convention above
because the term “Resource Model” refers to a specific Arches
construct, as explained in the Resource Model Overview above.
Graph Definition#
A Graph is a collection of NodeGroups, Nodes, and Edges which connect the Nodes.
Note
This definition does not include UI models and attributes, which are discussed below.
In the Arches data model, Nodes represent their graph data structure namesakes, sometimes called vertices. A Node does the work of defining the Graph data structure in conjunction with one or more Edges, and sometimes collecting data.
NodeGroups are an Arches feature used to represent a group of one or more Nodes that collect data. NodeGroups can be nested, creating a metadata structure which is used to display the graph in the UI and collect related information together.
A NodeGroup exists for every Node that collects data, and both
contains and shares its UUID with that node (see
naming conventions for references). NodeGroups with more than
one member Node are used to collect composite or semantically-related
information. For example, a NodeGroup for a Node named Name.E1
may
contain a Name Type.E55
Node. This way, a Graph with this
NodeGroup may store Names with multiple “types”, always collecting the
information together.
NodeGroups are used to create Cards, and this is done
based on the cardinality
property. Therefore, not every NodeGroup
will be used to create a Card, which allows NodeGroups to exist within
other NodeGroups. The parentnodegroup
property is used to record
this nesting.
A user-defined Function may be registered and then associated with a Graph in order to extend the behavior of Arches. For more information, see here.
GraphModel#
class GraphModel(models.Model):
graphid = models.UUIDField(primary_key=True, default=uuid.uuid1)
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
deploymentfile = models.TextField(blank=True, null=True)
author = models.TextField(blank=True, null=True)
deploymentdate = models.DateTimeField(blank=True, null=True)
version = models.TextField(blank=True, null=True)
isresource = models.BooleanField()
isactive = models.BooleanField()
iconclass = models.TextField(blank=True, null=True)
color = models.TextField(blank=True, null=True)
subtitle = models.TextField(blank=True, null=True)
ontology = models.ForeignKey('Ontology', db_column='ontologyid', related_name='graphs', null=True, blank=True)
functions = models.ManyToManyField(to='Function', through='FunctionXGraph')
jsonldcontext = models.TextField(blank=True, null=True)
template = models.ForeignKey(
'ReportTemplate',
db_column='templateid',
default='50000000-0000-0000-0000-000000000001'
)
config = JSONField(db_column='config', default={})
@property
def disable_instance_creation(self):
if not self.isresource:
return _('Only resource models may be edited - branches are not editable')
if not self.isactive:
return _('Set resource model status to Active in Graph Designer')
return False
def is_editable(self):
result = True
if self.isresource:
resource_instances = ResourceInstance.objects.filter(graph_id=self.graphid).count()
result = False if resource_instances > 0 else True
if settings.OVERRIDE_RESOURCE_MODEL_LOCK == True:
result = True
return result
class Meta:
managed = True
db_table = 'graphs'
Node#
class Node(models.Model):
"""
Name is unique across all resources because it ties a node to values within tiles. Recommend prepending resource class to node name.
"""
nodeid = models.UUIDField(primary_key=True, default=uuid.uuid1)
name = models.TextField()
description = models.TextField(blank=True, null=True)
istopnode = models.BooleanField()
ontologyclass = models.TextField(blank=True, null=True)
datatype = models.TextField()
nodegroup = models.ForeignKey(NodeGroup, db_column='nodegroupid', blank=True, null=True)
graph = models.ForeignKey(GraphModel, db_column='graphid', blank=True, null=True)
config = JSONField(blank=True, null=True, db_column='config')
issearchable = models.BooleanField(default=True)
isrequired = models.BooleanField(default=False)
sortorder = models.IntegerField(blank=True, null=True, default=0)
def get_child_nodes_and_edges(self):
"""
gather up the child nodes and edges of this node
returns a tuple of nodes and edges
"""
nodes = []
edges = []
for edge in Edge.objects.filter(domainnode=self):
nodes.append(edge.rangenode)
edges.append(edge)
child_nodes, child_edges = edge.rangenode.get_child_nodes_and_edges()
nodes.extend(child_nodes)
edges.extend(child_edges)
return (nodes, edges)
@property
def is_collector(self):
return str(self.nodeid) == str(self.nodegroup_id) and self.nodegroup is not None
def get_relatable_resources(self):
relatable_resource_ids = [
r2r.resourceclassfrom for r2r in Resource2ResourceConstraint.objects.filter(resourceclassto_id=self.nodeid)]
relatable_resource_ids = relatable_resource_ids + \
[r2r.resourceclassto for r2r in Resource2ResourceConstraint.objects.filter(
resourceclassfrom_id=self.nodeid)]
return relatable_resource_ids
def set_relatable_resources(self, new_ids):
old_ids = [res.nodeid for res in self.get_relatable_resources()]
for old_id in old_ids:
if old_id not in new_ids:
Resource2ResourceConstraint.objects.filter(Q(resourceclassto_id=self.nodeid) | Q(
resourceclassfrom_id=self.nodeid), Q(resourceclassto_id=old_id) | Q(resourceclassfrom_id=old_id)).delete()
for new_id in new_ids:
if new_id not in old_ids:
new_r2r = Resource2ResourceConstraint.objects.create(
resourceclassfrom_id=self.nodeid, resourceclassto_id=new_id)
new_r2r.save()
class Meta:
managed = True
db_table = 'nodes'
NodeGroup#
class NodeGroup(models.Model):
nodegroupid = models.UUIDField(primary_key=True, default=uuid.uuid1)
legacygroupid = models.TextField(blank=True, null=True)
cardinality = models.TextField(blank=True, default='1')
parentnodegroup = models.ForeignKey('self', db_column='parentnodegroupid',
blank=True, null=True) # Allows nodegroups within nodegroups
class Meta:
managed = True
db_table = 'node_groups'
default_permissions = ()
permissions = (
('read_nodegroup', 'Read'),
('write_nodegroup', 'Create/Update'),
('delete_nodegroup', 'Delete'),
('no_access_to_nodegroup', 'No Access'),
)
Edge#
class Edge(models.Model):
edgeid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
ontologyproperty = models.TextField(blank=True, null=True)
domainnode = models.ForeignKey('Node', db_column='domainnodeid', related_name='edge_domains')
rangenode = models.ForeignKey('Node', db_column='rangenodeid', related_name='edge_ranges')
graph = models.ForeignKey('GraphModel', db_column='graphid', blank=True, null=True)
class Meta:
managed = True
db_table = 'edges'
unique_together = (('rangenode', 'domainnode'),)
Function#
class Function(models.Model):
functionid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
name = models.TextField(blank=True, null=True)
functiontype = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
defaultconfig = JSONField(blank=True, null=True)
modulename = models.TextField(blank=True, null=True)
classname = models.TextField(blank=True, null=True)
component = models.TextField(blank=True, null=True, unique=True)
class Meta:
managed = True
db_table = 'functions'
@property
def defaultconfig_json(self):
json_string = json.dumps(self.defaultconfig)
return json_string
def get_class_module(self):
mod_path = self.modulename.replace('.py', '')
module = None
import_success = False
import_error = None
for function_dir in settings.FUNCTION_LOCATIONS:
try:
module = importlib.import_module(function_dir + '.%s' % mod_path)
import_success = True
except ImportError as e:
import_error = e
if module is not None:
break
if import_success == False:
print('Failed to import ' + mod_path)
print(import_error)
func = getattr(module, self.classname)
return func
Ontologies#
An ontology standardizes a set of valid CRM (Conceptual Reference Model) classes for Node instances, as well as a set of relationships that will define Edge instances. Most importantly, an ontology enforces which Edges can be used to connect which Nodes. If a pre-loaded ontology is designated for a Graph instance, every NodeGroup within that Graph must conform to that ontology. You may also create an “ontology-less” graph, which will not define specific CRM classes for the Nodes and Edges.
These rules are stored as OntologyClass instances, which are stored as JSON. These JSON objects consist of dictionaries with two properties, down and up, each of which contains another two properties ontology_property and ontology_classes (down assumes a known domain class, while up assumes a known range class).
{
"down":[
{
"ontology_property":"P1_is_identified_by",
"ontology_classes": [
"E51_Contact_Point",
"E75_Conceptual_Object_Appellation",
"E42_Identifier",
"E45_Address",
"E41_Appellation"
]
}
],
"up":[
{
"ontology_property":"P1_identifies",
"ontology_classes":[
"E51_Contact_Point",
"E75_Conceptual_Object_Appellation",
"E42_Identifier"
]
}
]
}
Aches comes preloaded with the CIDOC CRM, an ontology created by ICOM (International Council of Museums) to model cultural heritage documentation. However, a developer may create and load an entirely new ontology.
Ontology#
class Ontology(models.Model):
ontologyid = models.UUIDField(default=uuid.uuid1, primary_key=True)
name = models.TextField()
version = models.TextField()
path = models.FileField(storage=get_ontology_storage_system())
parentontology = models.ForeignKey('Ontology', db_column='parentontologyid',
related_name='extensions', null=True, blank=True)
class Meta:
managed = True
db_table = 'ontologies'
OntologyClass#
class OntologyClass(models.Model):
"""
the target JSONField has this schema:
values are dictionaries with 2 properties, 'down' and 'up' and within each of those another 2 properties,
'ontology_property' and 'ontology_classes'
"down" assumes a known domain class, while "up" assumes a known range class
.. code-block:: python
"down":[
{
"ontology_property": "P1_is_identified_by",
"ontology_classes": [
"E51_Contact_Point",
"E75_Conceptual_Object_Appellation",
"E42_Identifier",
"E45_Address",
"E41_Appellation",
....
]
}
]
"up":[
"ontology_property": "P1i_identifies",
"ontology_classes": [
"E51_Contact_Point",
"E75_Conceptual_Object_Appellation",
"E42_Identifier"
....
]
}
]
"""
ontologyclassid = models.UUIDField(default=uuid.uuid1, primary_key=True)
source = models.TextField()
target = JSONField(null=True)
ontology = models.ForeignKey('Ontology', db_column='ontologyid', related_name='ontologyclasses')
class Meta:
managed = True
db_table = 'ontologyclasses'
unique_together = (('source', 'ontology'),)
RDM Models#
The RDM (Reference Data Manager) stores all of the vocabularies used in your Arches installation. Whether they are simple wordlists or a polyhierarchical thesauri, these vocabularies are stored as “concept schemes” and can be viewed as an aggregation of one or more concepts and the semantic relationships (links) between those concepts.
In the data model, a concept scheme consists of a set of Concept
instances, each paired with a Value. In our running name/name_type
example, the Name Type.E55
Node would be linked to a Concept
(Name Type.E55
) which would have two child Concepts. Thus, where
the user sees a dropdown containing “Primary” and “Alternate”, these
are actually the Values of Name Type.E55
’s two descendent
Concepts. The parent/child relationships between Concepts are stored
as Relation instances.
Concept#
class Concept(models.Model):
conceptid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
nodetype = models.ForeignKey('DNodeType', db_column='nodetype')
legacyoid = models.TextField(unique=True)
class Meta:
managed = True
db_table = 'concepts'
Relation#
class Relation(models.Model):
conceptfrom = models.ForeignKey(Concept, db_column='conceptidfrom', related_name='relation_concepts_from')
conceptto = models.ForeignKey(Concept, db_column='conceptidto', related_name='relation_concepts_to')
relationtype = models.ForeignKey(DRelationType, db_column='relationtype')
relationid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
class Meta:
managed = True
db_table = 'relations'
unique_together = (('conceptfrom', 'conceptto', 'relationtype'),)
Value#
class Value(models.Model):
valueid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
concept = models.ForeignKey('Concept', db_column='conceptid')
valuetype = models.ForeignKey(DValueType, db_column='valuetype')
value = models.TextField()
language = models.ForeignKey(DLanguage, db_column='languageid', blank=True, null=True)
class Meta:
managed = True
db_table = 'values'
Resource Data#
Three models are used to store Arches business data:
ResourceInstance
- one per resource in the databaseTile
- stores all business dataResourceXResource
- records relationships between resource instances
Creating a new resource in the database instantiates a new
ResourceInstance, which belongs to one resource model and has a
unique resourceinstanceid
. A resource instance may also have its
own security/permissions properties in order to allow a fine-grained
level of user-based permissions.
Once data have been captured, they are stored as Tiles in the
database. Each Tile stores one instance of all of the attributes of a
given NodeGroup for a resource instance, as referenced by the
resourceinstanceid
. This business data is stored as a JSON object,
which is a dictionary with n number of keys/value pairs that represent
a Node’s id nodeid
and that Node’s value.
in theory:
{
"nodeid": "node value",
"nodeid": "node value"
}
in practice:
{
"20000000-0000-0000-0000-000000000002": "John",
"20000000-0000-0000-0000-000000000004": "Primary"
}
(In keeping with our running example, the keys in the second example
would refer to an Name.E1
node and an Name Type.E55
node,
respectively.)
Arches also allows for the creation of relationships between resource
instances, and these are stored as instances of the
ResourceXResource model. The resourceinstanceidfrom
and
resourceinstanceidto
fields create the relationship, and
relationshiptype
qualifies the relationship. The latter must
correspond to the appropriate top node in the RDM. This constrains the
list of available types of relationships available between resource
instances.
ResourceInstance#
class ResourceInstance(models.Model):
resourceinstanceid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
graph = models.ForeignKey(GraphModel, db_column='graphid')
legacyid = models.TextField(blank=True, unique=True, null=True)
createdtime = models.DateTimeField(auto_now_add=True)
class Meta:
managed = True
db_table = 'resource_instances'
TileModel#
class TileModel(models.Model): # Tile
"""
the data JSONField has this schema:
values are dictionaries with n number of keys that represent nodeid's and values the value of that node instance
.. code-block:: python
{
nodeid: node value,
nodeid: node value,
...
}
{
"20000000-0000-0000-0000-000000000002": "John",
"20000000-0000-0000-0000-000000000003": "Smith",
"20000000-0000-0000-0000-000000000004": "Primary"
}
the provisionaledits JSONField has this schema:
values are dictionaries with n number of keys that represent nodeid's and values the value of that node instance
.. code-block:: python
{
userid: {
value: node value,
status: "review", "approved", or "rejected"
action: "create", "update", or "delete"
reviewer: reviewer's user id,
timestamp: time of last provisional change,
reviewtimestamp: time of review
}
...
}
{
1: {
"value": {
"20000000-0000-0000-0000-000000000002": "Jack",
"20000000-0000-0000-0000-000000000003": "Smith",
"20000000-0000-0000-0000-000000000004": "Primary"
},
"status": "rejected",
"action": "update",
"reviewer": 8,
"timestamp": "20180101T1500",
"reviewtimestamp": "20180102T0800",
},
15: {
"value": {
"20000000-0000-0000-0000-000000000002": "John",
"20000000-0000-0000-0000-000000000003": "Smith",
"20000000-0000-0000-0000-000000000004": "Secondary"
},
"status": "review",
"action": "update",
}
"""
tileid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
resourceinstance = models.ForeignKey(ResourceInstance, db_column='resourceinstanceid')
parenttile = models.ForeignKey('self', db_column='parenttileid', blank=True, null=True)
data = JSONField(blank=True, null=True, db_column='tiledata') # This field type is a guess.
nodegroup = models.ForeignKey(NodeGroup, db_column='nodegroupid')
sortorder = models.IntegerField(blank=True, null=True, default=0)
provisionaledits = JSONField(blank=True, null=True, db_column='provisionaledits') # This field type is a guess.
class Meta:
managed = True
db_table = 'tiles'
def save(self, *args, **kwargs):
if(self.sortorder is None or (self.provisionaledits is not None and self.data == {})):
sortorder_max = TileModel.objects.filter(
nodegroup_id=self.nodegroup_id, resourceinstance_id=self.resourceinstance_id).aggregate(Max('sortorder'))['sortorder__max']
self.sortorder = sortorder_max + 1 if sortorder_max is not None else 0
super(TileModel, self).save(*args, **kwargs) # Call the "real" save() method.
ResourceXResource#
class ResourceXResource(models.Model):
resourcexid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
resourceinstanceidfrom = models.ForeignKey(
'ResourceInstance', db_column='resourceinstanceidfrom', blank=True, null=True, related_name='resxres_resource_instance_ids_from')
resourceinstanceidto = models.ForeignKey(
'ResourceInstance', db_column='resourceinstanceidto', blank=True, null=True, related_name='resxres_resource_instance_ids_to')
notes = models.TextField(blank=True, null=True)
relationshiptype = models.TextField(blank=True, null=True)
datestarted = models.DateField(blank=True, null=True)
dateended = models.DateField(blank=True, null=True)
created = models.DateTimeField()
modified = models.DateTimeField()
def delete(self):
from arches.app.search.search_engine_factory import SearchEngineFactory
se = SearchEngineFactory().create()
se.delete(index='resource_relations', doc_type='all', id=self.resourcexid)
super(ResourceXResource, self).delete()
def save(self):
from arches.app.search.search_engine_factory import SearchEngineFactory
se = SearchEngineFactory().create()
if not self.created:
self.created = datetime.datetime.now()
self.modified = datetime.datetime.now()
document = model_to_dict(self)
se.index_data(index='resource_relations', doc_type='all', body=document, idfield='resourcexid')
super(ResourceXResource, self).save()
class Meta:
managed = True
db_table = 'resource_x_resource'
Edit Log#
A change in a Tile’s contents, which is the result of any resource edits, is recorded as an instance of the EditLog model.
class EditLog(models.Model):
editlogid = models.UUIDField(primary_key=True, default=uuid.uuid1)
resourcedisplayname = models.TextField(blank=True, null=True)
resourceclassid = models.TextField(blank=True, null=True)
resourceinstanceid = models.TextField(blank=True, null=True)
nodegroupid = models.TextField(blank=True, null=True)
tileinstanceid = models.TextField(blank=True, null=True)
edittype = models.TextField(blank=True, null=True)
newvalue = JSONField(blank=True, null=True, db_column='newvalue')
oldvalue = JSONField(blank=True, null=True, db_column='oldvalue')
newprovisionalvalue = JSONField(blank=True, null=True, db_column='newprovisionalvalue')
oldprovisionalvalue = JSONField(blank=True, null=True, db_column='oldprovisionalvalue')
timestamp = models.DateTimeField(blank=True, null=True)
userid = models.TextField(blank=True, null=True)
user_firstname = models.TextField(blank=True, null=True)
user_lastname = models.TextField(blank=True, null=True)
user_email = models.TextField(blank=True, null=True)
user_username = models.TextField(blank=True, null=True)
provisional_userid = models.TextField(blank=True, null=True)
provisional_user_username = models.TextField(blank=True, null=True)
provisional_edittype = models.TextField(blank=True, null=True)
note = models.TextField(blank=True, null=True)
class Meta:
managed = True
db_table = 'edit_log'
UI Component Models#
A number of models exist specifically to support the resource model UI. The purpose of this is to create direct relationships between the resource graph and the data entry cards that are used to create resource instances. Generally, the process works like this:
A resource graph is an organized collection of NodeGroups which define what information will be gathered for a given resource model.
A resource’s Cards and are tied to specific NodeGroups and define which input Widgets will be used to gather values for each Node in that NodeGroup. Card Components are used to render the cards in various contexts in the Arches UI.
Cards are UI representations of a NodeGroup, and they encapsulate the Widgets that facilitate data entry for each Node in a given NodeGroup instance.
While a Card will only handle data entry for a single NodeGroup (which may have many Nodes or NodeGroups), a single NodeGroup can be handled by more than one Card.
Throughout the Arches UI, Card Components are used to render Cards in both read-only and data entry contexts.
Note
Beginning in Arches 4.3, Card Components provide functionality formerly provided by Forms, Menus, and Reports.
CardModel#
class CardModel(models.Model):
cardid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
instructions = models.TextField(blank=True, null=True)
cssclass = models.TextField(blank=True, null=True)
helpenabled = models.BooleanField(default=False)
helptitle = models.TextField(blank=True, null=True)
helptext = models.TextField(blank=True, null=True)
nodegroup = models.ForeignKey('NodeGroup', db_column='nodegroupid')
graph = models.ForeignKey('GraphModel', db_column='graphid')
active = models.BooleanField(default=True)
visible = models.BooleanField(default=True)
sortorder = models.IntegerField(blank=True, null=True, default=None)
component = models.ForeignKey('CardComponent', db_column='componentid', default=uuid.UUID(
'f05e4d3a-53c1-11e8-b0ea-784f435179ea'), on_delete=models.SET_DEFAULT)
config = JSONField(blank=True, null=True, db_column='config')
def is_editable(self):
result = True
tiles = TileModel.objects.filter(nodegroup=self.nodegroup).count()
result = False if tiles > 0 else True
if settings.OVERRIDE_RESOURCE_MODEL_LOCK == True:
result = True
return result
class Meta:
managed = True
db_table = 'cards'
Card Component#
A Card Component renders a Card.
class CardComponent(models.Model):
componentid = models.UUIDField(primary_key=True, default=uuid.uuid1)
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
component = models.TextField()
componentname = models.TextField()
defaultconfig = JSONField(blank=True, null=True, db_column='defaultconfig')
@property
def defaultconfig_json(self):
json_string = json.dumps(self.defaultconfig)
return json_string
class Meta:
managed = True
db_table = 'card_components'
Field description:
- name:
a name to be displayed in the UI for this component
- description:
a description to be displayed in the UI for this component
- component:
a require path for the JS module representing this component
- componentname:
a Knockout.js component name used by this component (for rendering via knockout’s component binding handler)
- defaultconfig:
a default JSON configuration object to be used by cards that implement this component
Widget#
class Widget(models.Model):
widgetid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess.
name = models.TextField(unique=True)
component = models.TextField(unique=True)
defaultconfig = JSONField(blank=True, null=True, db_column='defaultconfig')
helptext = models.TextField(blank=True, null=True)
datatype = models.TextField()
@property
def defaultconfig_json(self):
json_string = json.dumps(self.defaultconfig)
return json_string
class Meta:
managed = True
db_table = 'widgets'
DDataType#
Used to validate data entered into widgets
class DDataType(models.Model):
datatype = models.TextField(primary_key=True)
iconclass = models.TextField()
modulename = models.TextField(blank=True, null=True)
classname = models.TextField(blank=True, null=True)
defaultwidget = models.ForeignKey(db_column='defaultwidget', to='models.Widget', null=True)
defaultconfig = JSONField(blank=True, null=True, db_column='defaultconfig')
configcomponent = models.TextField(blank=True, null=True)
configname = models.TextField(blank=True, null=True)
issearchable = models.NullBooleanField(default=False)
isgeometric = models.BooleanField()
class Meta:
managed = True
db_table = 'd_data_types'
Naming Conventions#
id
vs _id
: ID as Primary Key vs Foreign Key#
Throughout the code, you will sometimes see an entity name with “id”
appended and other times see the same name with “_id” appended. For
example, you’ll see both nodegroupid
and nodegroup_id
.
What is the difference?
The first, nodegroupid
, is a UUID attribute in the database and is
the primary key for entities of type NodeGroup.
The second, nodegroup_id
, is a foreign key attribute (thus also a
UUID) that refers from somewhere else to a NodeGroup. For example, a
Tile object may have an associated NodeGroup; that NodeGroup object
itself would be referenced as tile.nodegroup
, and the NodeGroup’s
UUID – which in the context of a Tile object is a foreign key –
would therefore be tile.nodegroup_id
.
The reason to use tile.nodegroup_id
, instead of getting the
NodeGroup’s ID by going through the associated NodeGroup object with
tile.nodegroup.nodegroupid
, is that the latter would involve an
extra database query to fetch the NodeGroup instance, which would be a
waste if you don’t actually need the NodeGroup itself. When all you
need is the NodeGroup’s UUID – perhaps because you’re just going to
pass it along to something else that only needs the UUID – then
there’s no point fetching the entire NodeGroup when you already have
the Tile in hand and the Tile’s nodegroup_id
field is a foreign
key to the Tile’s associated NodeGroup. You might as well just get
that foreign key, tile.nodegroup_id
, directly.