class SerializerModel(models.Model): name = models.CharField(max_length=200) description = models.CharField(max_length=200) number = models.IntegerField(null=True) attributes = JSONAttributeField(null=True, blank=True) class Meta: app_label = 'core'
class Organization(models.Model): name = models.CharField(max_length=100) attrs = JSONAttributeField() class Meta: ordering = ('name', ) def __str__(self): return self.name
class Project(models.Model): name = models.CharField(max_length=100) organization = models.ForeignKey(Organization, on_delete=models.CASCADE) attrs = JSONAttributeField() class Meta: ordering = ('organization', 'name') def __str__(self): return self.name
class Department(models.Model): name = models.CharField(max_length=100) division = models.ForeignKey(Division, related_name='departments') attrs = JSONAttributeField() class Meta: ordering = ('division', 'name') def __str__(self): return self.name
class SpatialResource(RandomIDModel): class Meta: ordering = ('name',) class TutelaryMeta: perm_type = 'resource' path_fields = ('resource', 'pk') actions = ( ('resource.list', {'description': _("List resources"), 'permissions_object': 'resource', 'error_message': messages.RESOURCE_LIST}), ('resource.view', {'description': _("View resource"), 'error_message': messages.RESOURCE_VIEW}), ('resource.archive', {'description': _("Archive resource"), 'error_message': messages.RESOURCE_ARCHIVE}), ('resource.unarchive', {'description': _("Unarchive resource"), 'error_message': messages.RESOURCE_UNARCHIVE}), ) name = models.CharField(max_length=256, null=True, blank=True) time = models.DateTimeField(auto_now_add=True, null=True, blank=True) resource = models.ForeignKey( Resource, on_delete=models.CASCADE, related_name='spatial_resources' ) geom = GeometryCollectionField( srid=4326, blank=True, null=True ) attributes = JSONAttributeField(default={}) # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) def __repr__(self): repr_string = ('<SpatialResource id={obj.id}' ' resource={obj.resource.id}>') return repr_string.format(obj=self) @property def archived(self): return self.resource.archived @property def project(self): return self.resource.project
class Party(models.Model): department = models.ForeignKey(Department, related_name='parties') name = models.CharField(max_length=100) attrs = JSONAttributeField() class Meta: ordering = ('department', 'name') def get_absolute_url(self): return reverse('party-detail', kwargs={'pk': self.pk}) def __str__(self): return self.name
class Party(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE) name = models.CharField(max_length=100) attrs = JSONAttributeField() class Meta: ordering = ('project', 'name') def __str__(self): return self.name def get_absolute_url(self): return reverse('party-detail', kwargs={'pk': self.pk})
class Contract(models.Model): department = models.ForeignKey(Department, related_name='contracts') responsible = models.ForeignKey(Party) attrs = JSONAttributeField() class Meta: ordering = ('department', 'pk') def get_absolute_url(self): return reverse('parcel-detail', kwargs={'pk': self.pk}) def __str__(self): return str(self.pk)
class Parcel(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE) type = models.CharField(max_length=20) address = models.CharField(max_length=200) attrs = JSONAttributeField() class Meta: ordering = ('project', 'address') def __str__(self): return self.address def get_absolute_url(self): return reverse('parcel-detail', kwargs={'pk': self.pk})
class Party(ResourceModelMixin, RandomIDModel): """ Party model. A single party: has a name, a type, a type-dependent set of attributes and relationships with other parties and spatial units (i.e. tenure relationships). """ # Possible party types: TYPE_CHOICES is the well-known name used # by the JSONAttributesField field type to manage the range of # allowed attribute fields. INDIVIDUAL = 'IN' CORPORATION = 'CO' GROUP = 'GR' TYPE_CHOICES = ((INDIVIDUAL, __('Individual')), (CORPORATION, __('Corporation')), (GROUP, __('Group'))) # All parties are associated with a single project. project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='parties') # All parties have a name: for individuals, this is the full name, # while for groups and corporate entities, it's whatever name is # conventionally used to identify the organisation. name = models.CharField(max_length=200) # Party type: used to manage range of allowed attributes. type = models.CharField(max_length=2, choices=TYPE_CHOICES, default=INDIVIDUAL) contacts = JSONField(validators=[validate_contact], default={}) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) # Party-party relationships: includes things like family # relationships and group memberships. relationships = models.ManyToManyField('self', through='PartyRelationship', through_fields=('party1', 'party2'), symmetrical=False, related_name='relationships_set') # Tenure relationships. tenure_relationships = models.ManyToManyField( SpatialUnit, through='TenureRelationship', related_name='tenure_relationships') # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) history = HistoricalRecords() class Meta: ordering = ('name', ) class TutelaryMeta: perm_type = 'party' path_fields = ('project', 'id') actions = (('party.list', { 'description': _("List existing parties of a project"), 'error_message': messages.PARTY_LIST, 'permissions_object': 'project' }), ('party.create', { 'description': _("Add a party to a project"), 'error_message': messages.PARTY_CREATE, 'permissions_object': 'project' }), ('party.view', { 'description': _("View an existing party"), 'error_message': messages.PARTY_VIEW }), ('party.update', { 'description': _("Update an existing party"), 'error_message': messages.PARTY_UPDATE }), ('party.delete', { 'description': _("Delete an existing party"), 'error_message': messages.PARTY_DELETE }), ('party.resources.add', { 'description': _("Add resources to the party"), 'error_message': messages.PARTY_RESOURCES_ADD })) def __str__(self): return "<Party: {}>".format(self.name) def __repr__(self): repr_string = ('<Party id={obj.id} name={obj.name}' ' project={obj.project.slug}' ' type={obj.type}>') return repr_string.format(obj=self) @property def ui_class_name(self): return _("Party") def get_absolute_url(self): return iri_to_uri( reverse( 'parties:detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'party': self.id, }, ))
class TenureRelationship(ResourceModelMixin, RandomIDModel): """TenureRelationship model. Governs relationships between Party and SpatialUnit. """ # All tenure relationships are associated with a single project project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tenure_relationships') # Party to the relationship party = models.ForeignKey(Party, on_delete=models.CASCADE) # Spatial unit in the relationship spatial_unit = models.ForeignKey(SpatialUnit, on_delete=models.CASCADE) # Tenure relationships type: used to manage range of allowed attributes tenure_type = models.CharField(max_length=10) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) objects = managers.TenureRelationshipManager() # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) history = HistoricalRecords() class TutelaryMeta: perm_type = 'tenure_rel' path_fields = ('project', 'id') actions = ( ('tenure_rel.list', { 'description': _("List existing tenure relationships" " of a project"), 'error_message': messages.TENURE_REL_LIST, 'permissions_object': 'project' }), ('tenure_rel.create', { 'description': _("Add a tenure relationship to a project"), 'error_message': messages.TENURE_REL_CREATE, 'permissions_object': 'project' }), ('tenure_rel.view', { 'description': _("View an existing tenure relationship"), 'error_message': messages.TENURE_REL_VIEW }), ('tenure_rel.update', { 'description': _("Update an existing tenure relationship"), 'error_message': messages.TENURE_REL_UPDATE }), ('tenure_rel.delete', { 'description': _("Delete an existing tenure relationship"), 'error_message': messages.TENURE_REL_DELETE }), ('tenure_rel.resources.add', { 'description': _("Add a resource to a tenure relationship"), 'error_message': messages.TENURE_REL_RESOURCES_ADD }), ) def __str__(self): return "<TenureRelationship: {}>".format(self.name) def __repr__(self): repr_string = ('<TenureRelationship id={obj.id}' ' party={obj.party.id}' ' spatial_unit={obj.spatial_unit.id}' ' project={obj.project.slug}' ' tenure_type={obj.tenure_type}>') return repr_string.format(obj=self) @property def name(self): return "<{party}> {type} <{su}>".format( party=self.party.name, su=self.spatial_unit.name, type=self.tenure_type, ) @property def ui_class_name(self): return _("Relationship") def get_absolute_url(self): return iri_to_uri( reverse( 'parties:relationship_detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'relationship': self.id, }, )) @cached_property def tenure_type_label(self): if not self.project.current_questionnaire: return dict(TENURE_RELATIONSHIP_TYPES)[self.tenure_type] question = Question.objects.get( questionnaire_id=self.project.current_questionnaire, name='tenure_type') label = question.options.get(name=self.tenure_type).label_xlat if label is None or isinstance(label, str): return label else: return label.get(get_language(), label[question.questionnaire.default_language])
class PartyRelationship(RandomIDModel): """ PartyRelationship model. A relationship between parties: encodes simple logical terms like ``party1 is-spouse-of party2`` or ``party1 is-member-of party2``. May have additional type-dependent attributes. """ # Possible party relationship types: TYPE_CHOICES is the # well-known name used by the JSONAttributesField field type to # manage the range of allowed attribute fields. TYPE_CHOICES = (('S', 'is-spouse-of'), ('C', 'is-child-of'), ('M', 'is-member-of')) # All party relationships are associated with a single project project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='party_relationships') # Parties to the relationship. party1 = models.ForeignKey(Party, on_delete=models.CASCADE, related_name='party1') party2 = models.ForeignKey(Party, on_delete=models.CASCADE, related_name='party2') # Party relationship type: used to manage range of allowed attributes. type = models.CharField(max_length=1, choices=TYPE_CHOICES) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) objects = managers.PartyRelationshipManager() # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) history = HistoricalRecords() class TutelaryMeta: perm_type = 'party_rel' path_fields = ('project', 'id') actions = ( ('party_rel.list', { 'description': _("List existing party relationships" " of a project"), 'error_message': messages.PARTY_REL_LIST, 'permissions_object': 'project' }), ('party_rel.create', { 'description': _("Add a party relationship to a project"), 'error_message': messages.PARTY_REL_CREATE, 'permissions_object': 'project' }), ('party_rel.view', { 'description': _("View an existing party relationship"), 'error_message': messages.PARTY_REL_VIEW }), ('party_rel.update', { 'description': _("Update an existing party relationship"), 'error_message': messages.PARTY_REL_UPDATE }), ('party_rel.delete', { 'description': _("Delete an existing party relationship"), 'error_message': messages.PARTY_REL_DELETE }), ) def __str__(self): return "<PartyRelationship: <{party1}> {type} <{party2}>>".format( party1=self.party1.name, party2=self.party2.name, type=dict(self.TYPE_CHOICES).get(self.type)) def __repr__(self): repr_string = ('<PartyRelationship id={obj.id}' ' party1={obj.party1.id}' ' party2={obj.party2.id}' ' project={obj.project.slug}' ' type={obj.type}>') return repr_string.format(obj=self)
class TenureRelationship(ResourceModelMixin, RandomIDModel): """TenureRelationship model. Governs relationships between Party and SpatialUnit. """ CONTRACTUAL_SHARE_CROP = 'CS' CUSTOMARY_ARRANGEMENT = 'CA' GIFT = 'GF' HOMESTEAD = 'HS' INFORMAL_OCCUPANT = 'IO' INHERITANCE = 'IN' LEASEHOLD = 'LH' PURCHASED_FREEHOLD = 'PF' RENTAL = 'RN' OTHER = 'OT' ACQUIRED_CHOICES = ((CONTRACTUAL_SHARE_CROP, _('Contractual/Share Crop')), (CUSTOMARY_ARRANGEMENT, _('Customary Arrangement')), (GIFT, _('Gift')), (HOMESTEAD, _('Homestead')), (INFORMAL_OCCUPANT, _('Informal Occupant')), (INHERITANCE, _('Inheritance')), (LEASEHOLD, _('Leasehold')), (PURCHASED_FREEHOLD, _('Purchased Freehold')), (RENTAL, _('Rental')), (OTHER, _('Other'))) # All tenure relationships are associated with a single project project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name='tenure_relationships') # Party to the relationship party = models.ForeignKey(Party, on_delete=models.CASCADE) # Spatial unit in the relationship spatial_unit = models.ForeignKey(SpatialUnit, on_delete=models.CASCADE) # Tenure relationships type: used to manage range of allowed attributes tenure_type = models.ForeignKey( 'TenureRelationshipType', related_name='tenure_type', null=False, blank=False ) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) objects = managers.TenureRelationshipManager() history = HistoricalRecords() class TutelaryMeta: perm_type = 'tenure_rel' path_fields = ('project', 'id') actions = ( ('tenure_rel.list', {'description': _("List existing tenure relationships" " of a project"), 'error_message': messages.TENURE_REL_LIST, 'permissions_object': 'project'}), ('tenure_rel.create', {'description': _("Add a tenure relationship to a project"), 'error_message': messages.TENURE_REL_CREATE, 'permissions_object': 'project'}), ('tenure_rel.view', {'description': _("View an existing tenure relationship"), 'error_message': messages.TENURE_REL_VIEW}), ('tenure_rel.update', {'description': _("Update an existing tenure relationship"), 'error_message': messages.TENURE_REL_UPDATE}), ('tenure_rel.delete', {'description': _("Delete an existing tenure relationship"), 'error_message': messages.TENURE_REL_DELETE}), ('tenure_rel.resources.add', {'description': _("Add a resource to a tenure relationship"), 'error_message': messages.TENURE_REL_RESOURCES_ADD}), ) def __str__(self): return "<TenureRelationship: {}>".format(self.name) def __repr__(self): repr_string = ('<TenureRelationship id={obj.id}' ' party={obj.party.id}' ' spatial_unit={obj.spatial_unit.id}' ' project={obj.project.slug}' ' tenure_type={obj.tenure_type.id}>') return repr_string.format(obj=self) @property def name(self): return "<{party}> {type} <{su}>".format( party=self.party.name, su=self.spatial_unit.name, type=self.tenure_type_label, ) @property def ui_class_name(self): return _("Relationship") def get_absolute_url(self): return iri_to_uri(reverse( 'parties:relationship_detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'relationship': self.id, }, )) @property def tenure_type_label(self): return _(self.tenure_type.label)
class Labelled(models.Model): label = models.CharField(max_length=64) name = models.CharField(max_length=64) attrs = JSONAttributeField()
class SpatialRelationship(RandomIDModel): """A relationship between spatial units: encodes simple logical terms like ``su1 is-contained-in su2`` or ``su1 is-split-of su2``. May have additional requirements. """ # Possible spatial unit relationships types: TYPE_CHOICES is the # well-known name used by the JSONAttributesField field type to # manage the range of allowed attribute fields. TYPE_CHOICES = (('C', 'is-contained-in'), ('S', 'is-split-of'), ('M', 'is-merge-of')) # All spatial unit relationships are associated with a single project. project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='spatial_relationships') # Spatial units are in the relationships. su1 = models.ForeignKey(SpatialUnit, on_delete=models.CASCADE, related_name='spatial_unit_one') su2 = models.ForeignKey(SpatialUnit, on_delete=models.CASCADE, related_name='spatial_unit_two') # Spatial unit relationship type: used to manage range of allowed # attributes type = models.CharField(max_length=1, choices=TYPE_CHOICES) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) objects = managers.SpatialRelationshipManager() # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) history = HistoricalRecords() class TutelaryMeta: perm_type = 'spatial_rel' path_fields = ('project', 'id') actions = ( ('spatial_rel.list', { 'description': _("List existing spatial relationships" " of a project"), 'error_message': messages.SPATIAL_REL_LIST, 'permissions_object': 'project' }), ('spatial_rel.create', { 'description': _("Add a spatial relationship to a project"), 'error_message': messages.SPATIAL_REL_CREATE, 'permissions_object': 'project' }), ('spatial_rel.view', { 'description': _("View an existing spatial relationship"), 'error_message': messages.SPATIAL_REL_VIEW }), ('spatial_rel.update', { 'description': _("Update an existing spatial relationship"), 'error_message': messages.SPATIAL_REL_UPDATE }), ('spatial_rel.delete', { 'description': _("Delete an existing spatial relationship"), 'error_message': messages.SPATIAL_REL_DELETE }), ) def __str__(self): return "<SpatialRelationship: <{su1}> {type} <{su2}>>".format( su1=self.su1.name, su2=self.su2.name, type=dict(self.TYPE_CHOICES).get(self.type)) def __repr__(self): repr_string = ('<SpatialRelationship id={obj.id}' ' project={obj.project.slug}' ' su1={obj.su1_id}' ' su2={obj.su2_id}' ' type={obj.type}>') return repr_string.format(obj=self)
class SpatialUnit(ResourceModelMixin, RandomIDModel): """A single spatial unit: has a type, an optional geometry, a type-dependent set of attributes, and a set of relationships to other spatial units. """ # All spatial units are associated with a single project. project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='spatial_units') # Spatial unit type: used to manage range of allowed attributes. type = models.CharField(max_length=10) # Spatial unit geometry is optional: some spatial units may only # have a textual description of their location. geometry = GeometryField(null=True, geography=True) # Area, auto-calculated via trigger (see spatial/migrations/#0005) area = models.FloatField(default=0) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) # Spatial unit-spatial unit relationships: includes spatial # containment and split/merge relationships. relationships = models.ManyToManyField( 'self', through='SpatialRelationship', through_fields=('su1', 'su2'), symmetrical=False, related_name='relationships_set', ) # Audit history created_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) history = HistoricalRecords() class Meta: ordering = ('type', ) class TutelaryMeta: perm_type = 'spatial' path_fields = ('project', 'id') actions = (('spatial.list', { 'description': _("List existing spatial units of a project"), 'error_message': messages.SPATIAL_LIST, 'permissions_object': 'project' }), ('spatial.create', { 'description': _("Add a spatial unit to a project"), 'error_message': messages.SPATIAL_CREATE, 'permissions_object': 'project' }), ('spatial.view', { 'description': _("View an existing spatial unit"), 'error_message': messages.SPATIAL_VIEW }), ('spatial.update', { 'description': _("Update an existing spatial unit"), 'error_message': messages.SPATIAL_UPDATE }), ('spatial.delete', { 'description': _("Delete an existing spatial unit"), 'error_message': messages.SPATIAL_DELETE }), ('spatial.resources.add', { 'description': _("Add resources to this spatial unit"), 'error_message': messages.SPATIAL_ADD_RESOURCE })) def __str__(self): return "<SpatialUnit: {}>".format(self.name) def __repr__(self): repr_string = ('<SpatialUnit id={obj.id}' ' project={obj.project.slug}' ' type={obj.type}>') return repr_string.format(obj=self) @property def name(self): return self.location_type_label @property def ui_class_name(self): return _("Location") def get_absolute_url(self): return iri_to_uri( reverse( 'locations:detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'location': self.id, }, )) @cached_property def location_type_label(self): if not self.project.current_questionnaire: return dict(TYPE_CHOICES)[self.type] question = Question.objects.get( questionnaire_id=self.project.current_questionnaire, name='location_type') label = question.options.get(name=self.type).label_xlat if label is None or isinstance(label, str): return label else: return label.get(get_language(), label[question.questionnaire.default_language])
class SpatialUnit(ResourceModelMixin, RandomIDModel): """A single spatial unit: has a type, an optional geometry, a type-dependent set of attributes, and a set of relationships to other spatial units. """ # All spatial units are associated with a single project. project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='spatial_units') # Spatial unit type: used to manage range of allowed attributes. type = models.CharField(max_length=2, choices=TYPE_CHOICES, default='PA') # Spatial unit geometry is optional: some spatial units may only # have a textual description of their location. geometry = GeometryField(null=True) # JSON attributes field with management of allowed members. attributes = JSONAttributeField(default={}) # Spatial unit-spatial unit relationships: includes spatial # containment and split/merge relationships. relationships = models.ManyToManyField( 'self', through='SpatialRelationship', through_fields=('su1', 'su2'), symmetrical=False, related_name='relationships_set', ) history = HistoricalRecords() class Meta: ordering = ('type', ) class TutelaryMeta: perm_type = 'spatial' path_fields = ('project', 'id') actions = (('spatial.list', { 'description': _("List existing spatial units of a project"), 'error_message': messages.SPATIAL_LIST, 'permissions_object': 'project' }), ('spatial.create', { 'description': _("Add a spatial unit to a project"), 'error_message': messages.SPATIAL_CREATE, 'permissions_object': 'project' }), ('spatial.view', { 'description': _("View an existing spatial unit"), 'error_message': messages.SPATIAL_VIEW }), ('spatial.update', { 'description': _("Update an existing spatial unit"), 'error_message': messages.SPATIAL_UPDATE }), ('spatial.delete', { 'description': _("Delete an existing spatial unit"), 'error_message': messages.SPATIAL_DELETE }), ('spatial.resources.add', { 'description': _("Add resources to this spatial unit"), 'error_message': messages.SPATIAL_ADD_RESOURCE })) def __str__(self): return "<SpatialUnit: {}>".format(self.name) def __repr__(self): repr_string = ('<SpatialUnit id={obj.id}' ' project={obj.project.slug}' ' type={obj.type}>') return repr_string.format(obj=self) @property def name(self): return self.get_type_display() @property def ui_class_name(self): return _("Location") def get_absolute_url(self): return iri_to_uri( reverse( 'locations:detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'location': self.id, }, ))