def __init__(self, *args, **kwargs): super(SpatialQueryForm, self).__init__(*args, **kwargs) lookups = GeometryField.get_lookups() for lookup in self.data: if lookup in lookups: self.fields[lookup] = fields.GeometryField( required=False, widget=forms.BaseGeometryWidget()) break
def test_geometry_value_annotation(self): p = Point(1, 1, srid=4326) point = City.objects.annotate(p=Value(p, GeometryField( srid=4326))).first().p self.assertEqual(point, p)
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) # 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.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])
def output_field(self): return GeometryField(srid=self.source_expressions[0].field.srid)
class ObjectRecord(models.Model): index = models.PositiveIntegerField( default=1, help_text=_("Incremental index number of the object record."), ) object = models.ForeignKey(Object, on_delete=models.CASCADE, related_name="records") version = models.PositiveSmallIntegerField( _("version"), help_text=_("Version of the OBJECTTYPE for data in the object record"), ) data = JSONField( _("data"), help_text=_("Object data, based on OBJECTTYPE"), default=dict ) start_at = models.DateField( _("start at"), help_text=_("Legal start date of the object record") ) end_at = models.DateField( _("end at"), null=True, help_text=_("Legal end date of the object record") ) registration_at = models.DateField( _("registration at"), default=datetime.date.today, help_text=_("The date when the record was registered in the system"), ) correct = models.OneToOneField( "core.ObjectRecord", verbose_name="correction for", on_delete=models.CASCADE, related_name="corrected", null=True, blank=True, help_text=_("Object record which corrects the current record"), ) geometry = GeometryField( _("geometry"), blank=True, null=True, help_text=_( "Point, linestring or polygon object which represents the coordinates of the object" ), ) class Meta: unique_together = ("object", "index") def __str__(self): return f"{self.version} ({self.start_at})" def clean(self): super().clean() check_objecttype(self.object.object_type, self.version, self.data) def save(self, *args, **kwargs): if not self.id and self.object.last_record: self.index = self.object.last_record.index + 1 # add end_at to previous record previous_record = self.object.last_record previous_record.end_at = self.start_at previous_record.save() super().save(*args, **kwargs)
def test_geography_value(self): p = Polygon(((1, 1), (1, 2), (2, 2), (2, 1), (1, 1))) area = City.objects.annotate(a=functions.Area( Value(p, GeometryField(srid=4326, geography=True)))).first().a self.assertAlmostEqual(area.sq_km, 12305.1, 0)
class Zaak(AuditTrailMixin, APIMixin, models.Model): """ Modelleer de structuur van een ZAAK. Een samenhangende hoeveelheid werk met een welgedefinieerde aanleiding en een welgedefinieerd eindresultaat, waarvan kwaliteit en doorlooptijd bewaakt moeten worden. """ uuid = models.UUIDField(unique=True, default=uuid.uuid4, help_text="Unieke resource identifier (UUID4)") # Relate 'is_deelzaak_van' # De relatie vanuit een zaak mag niet verwijzen naar # dezelfde zaak d.w.z. moet verwijzen naar een andere # zaak. Die andere zaak mag geen relatie ?is deelzaak # van? hebben (d.w.z. deelzaken van deelzaken worden # niet ondersteund). hoofdzaak = models.ForeignKey( "self", limit_choices_to={"hoofdzaak__isnull": True}, null=True, blank=True, on_delete=models.CASCADE, related_name="deelzaken", verbose_name="is deelzaak van", help_text=_("URL-referentie naar de ZAAK, waarom verzocht is door de " "initiator daarvan, die behandeld wordt in twee of meer " "separate ZAAKen waarvan de onderhavige ZAAK er één is."), ) identificatie = models.CharField( max_length=40, blank=True, help_text="De unieke identificatie van de ZAAK binnen de organisatie " "die verantwoordelijk is voor de behandeling van de ZAAK.", validators=[alphanumeric_excluding_diacritic], db_index=True, ) bronorganisatie = RSINField( help_text="Het RSIN van de Niet-natuurlijk persoon zijnde de " "organisatie die de zaak heeft gecreeerd. Dit moet een geldig " "RSIN zijn van 9 nummers en voldoen aan " "https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef") omschrijving = models.CharField( max_length=80, blank=True, help_text="Een korte omschrijving van de zaak.") toelichting = models.TextField(max_length=1000, blank=True, help_text="Een toelichting op de zaak.") _zaaktype_url = models.URLField( _("extern zaaktype"), blank=True, max_length=1000, help_text=_( "URL-referentie naar extern ZAAKTYPE (in een andere Catalogi API)." ), ) _zaaktype = models.ForeignKey( "catalogi.ZaakType", on_delete=models.CASCADE, help_text="URL-referentie naar het ZAAKTYPE (in de Catalogi API).", null=True, blank=True, ) zaaktype = FkOrURLField( fk_field="_zaaktype", url_field="_zaaktype_url", help_text="URL-referentie naar het ZAAKTYPE (in de Catalogi API).", ) registratiedatum = models.DateField( help_text="De datum waarop de zaakbehandelende organisatie de ZAAK " "heeft geregistreerd. Indien deze niet opgegeven wordt, " "wordt de datum van vandaag gebruikt.", default=date.today, ) verantwoordelijke_organisatie = RSINField( help_text= "Het RSIN van de Niet-natuurlijk persoon zijnde de organisatie " "die eindverantwoordelijk is voor de behandeling van de " "zaak. Dit moet een geldig RSIN zijn van 9 nummers en voldoen aan " "https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef") startdatum = models.DateField( help_text="De datum waarop met de uitvoering van de zaak is gestart", db_index=True, ) einddatum = models.DateField( blank=True, null=True, help_text="De datum waarop de uitvoering van de zaak afgerond is.", ) einddatum_gepland = models.DateField( blank=True, null=True, help_text="De datum waarop volgens de planning verwacht wordt dat de " "zaak afgerond wordt.", ) uiterlijke_einddatum_afdoening = models.DateField( blank=True, null=True, help_text="De laatste datum waarop volgens wet- en regelgeving de zaak " "afgerond dient te zijn.", ) publicatiedatum = models.DateField( _("publicatiedatum"), null=True, blank=True, help_text=_( "Datum waarop (het starten van) de zaak gepubliceerd is of wordt." ), ) producten_of_diensten = ArrayField( models.URLField(_("URL naar product/dienst"), max_length=1000), default=list, help_text=_( "De producten en/of diensten die door de zaak worden voortgebracht. " "Dit zijn URLs naar de resources zoals die door de producten- " "en dienstencatalogus-API wordt ontsloten. " "De producten/diensten moeten bij het zaaktype vermeld zijn."), blank=True, ) communicatiekanaal = models.URLField( _("communicatiekanaal"), blank=True, max_length=1000, help_text= _("Het medium waarlangs de aanleiding om een zaak te starten is ontvangen. " "URL naar een communicatiekanaal in de VNG-Referentielijst van communicatiekanalen." ), ) vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField( _("vertrouwlijkheidaanduiding"), help_text= _("Aanduiding van de mate waarin het zaakdossier van de ZAAK voor de openbaarheid bestemd is." ), ) betalingsindicatie = models.CharField( _("betalingsindicatie"), max_length=20, blank=True, choices=BetalingsIndicatie.choices, help_text=_("Indicatie of de, met behandeling van de zaak gemoeide, " "kosten betaald zijn door de desbetreffende betrokkene."), ) laatste_betaaldatum = models.DateTimeField( _("laatste betaaldatum"), blank=True, null=True, help_text=_( "De datum waarop de meest recente betaling is verwerkt " "van kosten die gemoeid zijn met behandeling van de zaak."), ) zaakgeometrie = GeometryField( blank=True, null=True, help_text="Punt, lijn of (multi-)vlak geometrie-informatie.", ) verlenging_reden = models.CharField( _("reden verlenging"), max_length=200, blank=True, help_text= _("Omschrijving van de reden voor het verlengen van de behandeling van de zaak." ), ) verlenging_duur = DurationField( _("duur verlenging"), blank=True, null=True, help_text=_( "Het aantal werkbare dagen waarmee de doorlooptijd van de " "behandeling van de ZAAK is verlengd (of verkort) ten opzichte " "van de eerder gecommuniceerde doorlooptijd."), ) verlenging = GegevensGroepType({ "reden": verlenging_reden, "duur": verlenging_duur }) opschorting_indicatie = models.BooleanField( _("indicatie opschorting"), default=False, blank=True, help_text=_( "Aanduiding of de behandeling van de ZAAK tijdelijk is opgeschort." ), ) opschorting_reden = models.CharField( _("reden opschorting"), max_length=200, blank=True, help_text= _("Omschrijving van de reden voor het opschorten van de behandeling van de zaak." ), ) opschorting = GegevensGroepType({ "indicatie": opschorting_indicatie, "reden": opschorting_reden }) selectielijstklasse = models.URLField( _("selectielijstklasse"), blank=True, max_length=1000, help_text= _("URL-referentie naar de categorie in de gehanteerde 'Selectielijst Archiefbescheiden' die, gezien " "het zaaktype en het resultaattype van de zaak, bepalend is voor het archiefregime van de zaak." ), ) # Archiving archiefnominatie = models.CharField( _("archiefnominatie"), max_length=40, null=True, blank=True, choices=Archiefnominatie.choices, help_text= _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden." ), db_index=True, ) archiefstatus = models.CharField( _("archiefstatus"), max_length=40, choices=Archiefstatus.choices, default=Archiefstatus.nog_te_archiveren, help_text= _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden." ), db_index=True, ) archiefactiedatum = models.DateField( _("archiefactiedatum"), null=True, blank=True, help_text= _("De datum waarop het gearchiveerde zaakdossier vernietigd moet worden dan wel overgebracht moet " "worden naar een archiefbewaarplaats. Wordt automatisch berekend bij het aanmaken of wijzigen van " "een RESULTAAT aan deze ZAAK indien nog leeg."), db_index=True, ) objects = ZaakQuerySet.as_manager() class Meta: verbose_name = "zaak" verbose_name_plural = "zaken" unique_together = ("bronorganisatie", "identificatie") def __str__(self): return self.identificatie def save(self, *args, **kwargs): if not self.identificatie: self.identificatie = generate_unique_identification( self, "registratiedatum") if (self.betalingsindicatie == BetalingsIndicatie.nvt and self.laatste_betaaldatum): self.laatste_betaaldatum = None super().save(*args, **kwargs) @property def current_status_uuid(self): status = self.status_set.first() return status.uuid if status else None @property def is_closed(self) -> bool: return self.einddatum is not None def unique_representation(self): return f"{self.bronorganisatie} - {self.identificatie}"
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=100) # 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', ) # Denormalized duplication of label from the QuestionOption related to the # SpatialUnit label = JSONField(null=True) # 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): # Handle no questionnaire if (not self.project.current_questionnaire) or (self.label is None): return dict(TYPE_CHOICES)[self.type] # Handle non-translatable label if isinstance(self.label, str): return self.label # Handle translated label translated_label = self.label.get(get_language()) if translated_label: return translated_label # If label failed to translate, fallback to default language rel_questionnaire = Questionnaire.objects.get( id=self.project.current_questionnaire) return self.label.get(rel_questionnaire.default_language)
class LineSubstring(GeoFunc): output_field = GeometryField()
def test_deconstruct_empty(self): field = GeometryField() *_, kwargs = field.deconstruct() self.assertEqual(kwargs, {"srid": 4326})
def test_geometry_value_annotation_different_srid(self): p = Point(1, 1, srid=32140) point = City.objects.annotate(p=Value(p, GeometryField(srid=4326))).first().p self.assertTrue(point.equals_exact(p.transform(4326, clone=True), 10 ** -5)) self.assertEqual(point.srid, 4326)
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): return str(self) @property def name(self): return self.get_type_display() @property def ui_class_name(self): return _("Location") @property def ui_detail_url(self): return reverse( 'locations:detail', kwargs={ 'organization': self.project.organization.slug, 'project': self.project.slug, 'location': self.id, }, )
class ST_LineSubstring(Func): function = 'ST_LineSubstring' output_field = GeometryField()
class Zaak(APIMixin, models.Model): """ Modelleer de structuur van een ZAAK. Een samenhangende hoeveelheid werk met een welgedefinieerde aanleiding en een welgedefinieerd eindresultaat, waarvan kwaliteit en doorlooptijd bewaakt moeten worden. """ uuid = models.UUIDField(unique=True, default=uuid.uuid4, help_text="Unieke resource identifier (UUID4)") identificatie = models.CharField( max_length=40, blank=True, help_text='De unieke identificatie van de ZAAK binnen de organisatie ' 'die verantwoordelijk is voor de behandeling van de ZAAK.', validators=[alphanumeric_excluding_diacritic]) bronorganisatie = RSINField( help_text='Het RSIN van de Niet-natuurlijk persoon zijnde de ' 'organisatie die de zaak heeft gecreeerd. Dit moet een geldig ' 'RSIN zijn van 9 nummers en voldoen aan ' 'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef') omschrijving = models.CharField( max_length=80, blank=True, help_text='Een korte omschrijving van de zaak.') zaaktype = models.URLField( help_text="URL naar het zaaktype in de CATALOGUS waar deze voorkomt") registratiedatum = models.DateField( help_text='De datum waarop de zaakbehandelende organisatie de ZAAK ' 'heeft geregistreerd. Indien deze niet opgegeven wordt, ' 'wordt de datum van vandaag gebruikt.', default=date.today) verantwoordelijke_organisatie = RSINField( help_text= 'Het RSIN van de Niet-natuurlijk persoon zijnde de organisatie ' 'die eindverantwoordelijk is voor de behandeling van de ' 'zaak. Dit moet een geldig RSIN zijn van 9 nummers en voldoen aan ' 'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef') startdatum = models.DateField( help_text='De datum waarop met de uitvoering van de zaak is gestart') einddatum = models.DateField( blank=True, null=True, help_text='De datum waarop de uitvoering van de zaak afgerond is.', ) einddatum_gepland = models.DateField( blank=True, null=True, help_text='De datum waarop volgens de planning verwacht wordt dat de ' 'zaak afgerond wordt.', ) uiterlijke_einddatum_afdoening = models.DateField( blank=True, null=True, help_text='De laatste datum waarop volgens wet- en regelgeving de zaak ' 'afgerond dient te zijn.') toelichting = models.TextField(max_length=1000, blank=True, help_text='Een toelichting op de zaak.') zaakgeometrie = GeometryField( blank=True, null=True, help_text="Punt, lijn of (multi-)vlak geometrie-informatie.") class Meta: verbose_name = 'zaak' verbose_name_plural = 'zaken' unique_together = ('bronorganisatie', 'identificatie') def __str__(self): return self.identificatie def save(self, *args, **kwargs): if not self.identificatie: self.identificatie = str(uuid.uuid4()) super().save(*args, **kwargs) @property def current_status_uuid(self): status = self.status_set.order_by('-datum_status_gezet').first() return status.uuid if status else None
class Record(TaggedModel, AttachableModel, LinkableModel): name = models.CharField(max_length=255) date_collected = models.DateField() published = models.CharField( max_length=55, choices=[('published', 'Published'), ('draft', 'Draft')], default='draft' ) description = models.TextField(blank=True) feature = models.ForeignKey(Feature, on_delete=models.SET_NULL, blank=True, null=True, related_name='records') medium = models.CharField( choices=[ ('lake_sediment', 'Lake Sediment'), ('marine_sediment', 'Marine Sediment'), ('glacier_ice', 'Glacier Ice'), ('peat', 'Peat'), ('lake_water', 'Lake Water'), ('river_water', 'River Water'), ('ocean_water', 'Ocean Water'), ('wood', 'Wood'), ('surficial_sediment', 'Surficial sediment'), ('rock', 'Rock'), ('mollusk_shell', 'Mollusk shell'), ('coral', 'Coral'), ('speleothem', 'Speleothem'), ('sclerosponge', 'Sclerosponge'), ('air', 'Air'), ('hybrid', 'Hybrid'), ('other', 'Other') ], max_length=55 ) type = models.CharField( choices=[ ('samples', 'Samples'), ('core', 'Core'), ('section', 'Section'), ('sensor', 'Sensor'), ('other', 'Other') ], max_length=55 ) resolution = models.FloatField(blank=True, null=True, default=None) min_year = models.FloatField(blank=True, null=True, default=None) max_year = models.FloatField(blank=True, null=True, default=None) position_units = models.CharField(max_length=55, blank=True, default='cm') geometry = GeometryField(blank=True, null=True) geo_elev = models.FloatField(default=0) geo_error = models.FloatField(default=0) modified = models.DateTimeField('modified', auto_now=True) created = models.DateTimeField('created', auto_now_add=True) class Meta: ordering = ['-modified'] def save(self, *args, **kwargs): # keep the list of people who published this record synced # sort by earliest publication if not self.pk: super().save(*args, **kwargs) self.record_authorships.filter(role='published').delete() all_people = [] for ref in self.record_uses.order_by('publication__year'): for authorship in ref.publication.authorships.filter(role='author').order_by('order'): if authorship.person not in all_people: all_people.append(authorship.person) for i, person in enumerate(all_people): RecordAuthorship.objects.create( record=self, person=person, role='published', order=20 + i ) # saving at the end ensures the reversion signal gets sent return super().save(*args, **kwargs) def __str__(self): return self.name
class Zaak(APIMixin, models.Model): """ Modelleer de structuur van een ZAAK. Een samenhangende hoeveelheid werk met een welgedefinieerde aanleiding en een welgedefinieerd eindresultaat, waarvan kwaliteit en doorlooptijd bewaakt moeten worden. """ uuid = models.UUIDField(unique=True, default=uuid.uuid4, help_text="Unieke resource identifier (UUID4)") # Relate 'is_deelzaak_van' # De relatie vanuit een zaak mag niet verwijzen naar # dezelfde zaak d.w.z. moet verwijzen naar een andere # zaak. Die andere zaak mag geen relatie ?is deelzaak # van? hebben (d.w.z. deelzaken van deelzaken worden # niet ondersteund). hoofdzaak = models.ForeignKey( 'self', limit_choices_to={'hoofdzaak__isnull': True}, null=True, blank=True, on_delete=models.CASCADE, related_name='deelzaken', verbose_name='is deelzaak van', help_text=_("De verwijzing naar de ZAAK, waarom verzocht is door de " "initiator daarvan, die behandeld wordt in twee of meer " "separate ZAAKen waarvan de onderhavige ZAAK er één is.")) identificatie = models.CharField( max_length=40, blank=True, help_text='De unieke identificatie van de ZAAK binnen de organisatie ' 'die verantwoordelijk is voor de behandeling van de ZAAK.', validators=[alphanumeric_excluding_diacritic]) bronorganisatie = RSINField( help_text='Het RSIN van de Niet-natuurlijk persoon zijnde de ' 'organisatie die de zaak heeft gecreeerd. Dit moet een geldig ' 'RSIN zijn van 9 nummers en voldoen aan ' 'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef') omschrijving = models.CharField( max_length=80, blank=True, help_text='Een korte omschrijving van de zaak.') toelichting = models.TextField(max_length=1000, blank=True, help_text='Een toelichting op de zaak.') zaaktype = models.URLField( _("zaaktype"), help_text="URL naar het zaaktype in de CATALOGUS waar deze voorkomt", max_length=1000) registratiedatum = models.DateField( help_text='De datum waarop de zaakbehandelende organisatie de ZAAK ' 'heeft geregistreerd. Indien deze niet opgegeven wordt, ' 'wordt de datum van vandaag gebruikt.', default=date.today) verantwoordelijke_organisatie = RSINField( help_text= 'Het RSIN van de Niet-natuurlijk persoon zijnde de organisatie ' 'die eindverantwoordelijk is voor de behandeling van de ' 'zaak. Dit moet een geldig RSIN zijn van 9 nummers en voldoen aan ' 'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef') startdatum = models.DateField( help_text='De datum waarop met de uitvoering van de zaak is gestart') einddatum = models.DateField( blank=True, null=True, help_text='De datum waarop de uitvoering van de zaak afgerond is.', ) einddatum_gepland = models.DateField( blank=True, null=True, help_text='De datum waarop volgens de planning verwacht wordt dat de ' 'zaak afgerond wordt.', ) uiterlijke_einddatum_afdoening = models.DateField( blank=True, null=True, help_text='De laatste datum waarop volgens wet- en regelgeving de zaak ' 'afgerond dient te zijn.') publicatiedatum = models.DateField( _("publicatiedatum"), null=True, blank=True, help_text=_( "Datum waarop (het starten van) de zaak gepubliceerd is of wordt.") ) producten_of_diensten = ArrayField( models.URLField(_("URL naar product/dienst"), max_length=1000), default=list, help_text=_( "De producten en/of diensten die door de zaak worden voortgebracht. " "Dit zijn URLs naar de resources zoals die door de producten- " "en dienstencatalogus-API wordt ontsloten. " "De producten/diensten moeten bij het zaaktype vermeld zijn.")) communicatiekanaal = models.URLField( _("communicatiekanaal"), blank=True, max_length=1000, help_text= _("Het medium waarlangs de aanleiding om een zaak te starten is ontvangen. " "URL naar een communicatiekanaal in de VNG-Referentielijst van communicatiekanalen." )) vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField( _("vertrouwlijkheidaanduiding"), help_text= _("Aanduiding van de mate waarin het zaakdossier van de ZAAK voor de openbaarheid bestemd is." )) betalingsindicatie = models.CharField( _("betalingsindicatie"), max_length=20, blank=True, choices=BetalingsIndicatie.choices, help_text=_("Indicatie of de, met behandeling van de zaak gemoeide, " "kosten betaald zijn door de desbetreffende betrokkene.")) laatste_betaaldatum = models.DateTimeField( _("laatste betaaldatum"), blank=True, null=True, help_text=_( "De datum waarop de meest recente betaling is verwerkt " "van kosten die gemoeid zijn met behandeling van de zaak.")) zaakgeometrie = GeometryField( blank=True, null=True, help_text="Punt, lijn of (multi-)vlak geometrie-informatie.") verlenging_reden = models.CharField( _("reden verlenging"), max_length=200, blank=True, help_text= _("Omschrijving van de reden voor het verlengen van de behandeling van de zaak." )) verlenging_duur = DaysDurationField( _("duur verlenging"), blank=True, null=True, help_text=_( "Het aantal werkbare dagen waarmee de doorlooptijd van de " "behandeling van de ZAAK is verlengd (of verkort) ten opzichte " "van de eerder gecommuniceerde doorlooptijd.")) verlenging = GegevensGroepType({ 'reden': verlenging_reden, 'duur': verlenging_duur, }) opschorting_indicatie = models.BooleanField( _("indicatie opschorting"), default=False, help_text=_( "Aanduiding of de behandeling van de ZAAK tijdelijk is opgeschort." )) opschorting_reden = models.CharField( _("reden opschorting"), max_length=200, blank=True, help_text= _("Omschrijving van de reden voor het opschorten van de behandeling van de zaak." )) opschorting = GegevensGroepType({ 'indicatie': opschorting_indicatie, 'reden': opschorting_reden, }) selectielijstklasse = models.URLField( _("selectielijstklasse"), blank=True, max_length=1000, help_text= _("URL-referentie naar de categorie in de gehanteerde 'Selectielijst Archiefbescheiden' die, gezien " "het zaaktype en het resultaattype van de zaak, bepalend is voor het archiefregime van de zaak." )) relevante_andere_zaken = ArrayField(models.URLField( _("URL naar andere zaak"), max_length=1000), blank=True, default=list) # Archiving archiefnominatie = models.CharField( _("archiefnominatie"), max_length=40, null=True, blank=True, choices=Archiefnominatie.choices, help_text= _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden." )) archiefstatus = models.CharField( _("archiefstatus"), max_length=40, choices=Archiefstatus.choices, default=Archiefstatus.nog_te_archiveren, help_text= _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden." )) archiefactiedatum = models.DateField( _("archiefactiedatum"), null=True, blank=True, help_text= _("De datum waarop het gearchiveerde zaakdossier vernietigd moet worden dan wel overgebracht moet " "worden naar een archiefbewaarplaats. Wordt automatisch berekend bij het aanmaken of wijzigen van " "een RESULTAAT aan deze ZAAK indien nog leeg.")) class Meta: verbose_name = 'zaak' verbose_name_plural = 'zaken' unique_together = ('bronorganisatie', 'identificatie') def __str__(self): return self.identificatie def save(self, *args, **kwargs): if not self.identificatie: self.identificatie = str(uuid.uuid4()) if self.betalingsindicatie == BetalingsIndicatie.nvt and self.laatste_betaaldatum: self.laatste_betaaldatum = None super().save(*args, **kwargs) @property def current_status_uuid(self): status = self.status_set.order_by('-datum_status_gezet').first() return status.uuid if status else None def get_brondatum(self, afleidingswijze: str, datum_kenmerk: str = None, objecttype: str = None, procestermijn: str = None) -> date: """ To calculate the Archiefactiedatum, we first need the "brondatum" which is like the start date of the storage period. :param afleidingswijze: One of the `Afleidingswijze` choices. :param datum_kenmerk: A `string` representing an arbitrary attribute name. Currently only needed when `afleidingswijze` is `eigenschap` or `zaakobject`. :param objecttype: A `string` representing an arbitrary objecttype name. Currently only needed when `afleidingswijze` is `zaakobject`. :param procestermijn: A `string` representing an ISO8601 period that is considered the process term of the Zaak. Currently only needed when `afleidingswijze` is `termijn`. :return: A specific date that marks the start of the storage period, or `None`. """ if afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.afgehandeld: return self.einddatum elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.hoofdzaak: # TODO: Document that hoofdzaak can not an external zaak return self.hoofdzaak.einddatum if self.hoofdzaak else None elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.eigenschap: if not datum_kenmerk: raise DetermineProcessEndDateException( _('Geen datumkenmerk aanwezig om de eigenschap te achterhalen voor het bepalen van de brondatum.' )) eigenschap = self.zaakeigenschap_set.filter( _naam=datum_kenmerk).first() if eigenschap: if not eigenschap.waarde: return None try: return parse_isodatetime(eigenschap.waarde).date() except ValueError: raise DetermineProcessEndDateException( _('Geen geldige datumwaarde in eigenschap "{}": {}'). format(datum_kenmerk, eigenschap.waarde)) else: raise DetermineProcessEndDateException( _('Geen eigenschap gevonden die overeenkomt met het datumkenmerk "{}" voor het bepalen van de ' 'brondatum.').format(datum_kenmerk)) elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.ander_datumkenmerk: # The brondatum, and therefore the archiefactiedatum, needs to be determined manually. return None elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.zaakobject: if not objecttype: raise DetermineProcessEndDateException( _('Geen objecttype aanwezig om het zaakobject te achterhalen voor het bepalen van de brondatum.' )) if not datum_kenmerk: raise DetermineProcessEndDateException( _('Geen datumkenmerk aanwezig om het attribuut van het zaakobject te achterhalen voor het bepalen ' 'van de brondatum.')) for zaak_object in self.zaakobject_set.filter( object_type=objecttype): object = zaak_object._get_object() if datum_kenmerk in object: try: return parse_isodatetime(object[datum_kenmerk]).date() except ValueError: raise DetermineProcessEndDateException( _('Geen geldige datumwaarde in attribuut "{}": {}' ).format(datum_kenmerk, object[datum_kenmerk])) raise DetermineProcessEndDateException( _('Geen attribuut gevonden die overeenkomt met het datumkenmerk "{}" voor het bepalen van de ' 'brondatum.').format(datum_kenmerk)) elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.termijn: if self.einddatum is None: # TODO: Not sure if we should raise an error instead. return None if procestermijn is None: raise DetermineProcessEndDateException( _('Geen procestermijn aanwezig voor het bepalen van de brondatum.' )) try: return self.einddatum + isodate.parse_duration(procestermijn) except (ValueError, TypeError) as e: raise DetermineProcessEndDateException( _('Geen geldige periode in procestermijn: {}').format( procestermijn)) elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.gerelateerde_zaak: # TODO: Determine what this means... raise NotImplementedError elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.ingangsdatum_besluit: # TODO: Relation from Zaak to Besluit is not implemented yet... raise NotImplementedError elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.vervaldatum_besluit: # TODO: Relation from Zaak to Besluit is not implemented yet... raise NotImplementedError raise ValueError(f'Onbekende "Afleidingswijze": {afleidingswijze}')