diff --git a/bemas/models/models_objectclass.py b/bemas/models/models_objectclass.py index 92845cd9..f77e721a 100644 --- a/bemas/models/models_objectclass.py +++ b/bemas/models/models_objectclass.py @@ -12,7 +12,8 @@ from toolbox.constants_vars import standard_validators, personennamen_validators, \ d3_regex, d3_message, email_message, hausnummer_regex, hausnummer_message, \ postleitzahl_regex, postleitzahl_message, rufnummer_regex, rufnummer_message -from bemas.utils import LOG_ACTIONS, concat_address, shorten_string +from toolbox.utils import concat_address +from bemas.utils import LOG_ACTIONS, shorten_string from .base import GeometryObjectclass, Objectclass from .functions import store_complaint_search_content from .models_codelist import Sector, Status, TypeOfEvent, TypeOfImmission diff --git a/bemas/utils.py b/bemas/utils.py index 7875b5bc..34b882f4 100644 --- a/bemas/utils.py +++ b/bemas/utils.py @@ -19,28 +19,6 @@ } -def concat_address(street=None, house_number=None, postal_code=None, place=None): - """ - concats passed address string parts and returns address string - - :param street: street name - :param house_number: house number - :param postal_code: postal code - :param place: place - :return: address string - """ - first_part = (street + ' ' if street else '') + (house_number if house_number else '') - second_part = (postal_code + ' ' if postal_code else '') + (place if place else '') - if first_part and second_part: - return first_part.strip() + ', ' + second_part.strip() - elif first_part: - return first_part.strip() - elif second_part: - return second_part.strip() - else: - return None - - def format_date_datetime(value, time_string_only=False): """ formats date or datetime and returns appropriate date, datetime or time string diff --git a/datenmanagement/models/constants_vars.py b/datenmanagement/models/constants_vars.py index 619a8a4d..e6f345e2 100644 --- a/datenmanagement/models/constants_vars.py +++ b/datenmanagement/models/constants_vars.py @@ -2,6 +2,10 @@ # validators # +bevollmaechtigte_bezirksschornsteinfeger_bezirk_regex = r'^[A-Z]{3}-[0-9]{2}$' +bevollmaechtigte_bezirksschornsteinfeger_bezirk_message = 'Der Bezirk ' \ + 'muss aus drei Großbuchstaben, einem ' \ + 'Bindestrich und zwei Ziffern bestehen.' denksteine_nummer_regex = r'^[0-9]+[a-z]*$' denksteine_nummer_message = 'Die Nummer muss mit ' \ 'einer Ziffer beginnen und mit einer Ziffer ' \ diff --git a/datenmanagement/models/models_codelist.py b/datenmanagement/models/models_codelist.py index b58bbc57..cb8769b1 100644 --- a/datenmanagement/models/models_codelist.py +++ b/datenmanagement/models/models_codelist.py @@ -1,14 +1,18 @@ from decimal import Decimal from django.core.validators import EmailValidator, MaxValueValidator, MinValueValidator, \ RegexValidator -from django.db.models.fields import CharField, DecimalField +from django.db.models.fields import BooleanField, CharField, DateField, DecimalField -from toolbox.constants_vars import personennamen_validators, standard_validators, email_message +from toolbox.constants_vars import personennamen_validators, standard_validators, email_message, \ + hausnummer_regex, hausnummer_message, postleitzahl_regex, postleitzahl_message, \ + rufnummer_regex, rufnummer_message +from toolbox.utils import concat_address from .base import Metamodel, Codelist, Art, Befestigungsart, Material, Schlagwort, Status, Typ -from .constants_vars import fahrbahnwinterdienst_code_regex, fahrbahnwinterdienst_code_message, \ - haefen_abkuerzung_regex, haefen_abkuerzung_message, linien_linie_regex, linien_linie_message, \ - parkscheinautomaten_zone_regex, parkscheinautomaten_zone_message, quartiere_code_regex, \ - quartiere_code_message +from .constants_vars import bevollmaechtigte_bezirksschornsteinfeger_bezirk_regex, \ + bevollmaechtigte_bezirksschornsteinfeger_bezirk_message, fahrbahnwinterdienst_code_regex, \ + fahrbahnwinterdienst_code_message, haefen_abkuerzung_regex, haefen_abkuerzung_message, \ + linien_linie_regex, linien_linie_message, parkscheinautomaten_zone_regex, \ + parkscheinautomaten_zone_message, quartiere_code_regex, quartiere_code_message from .fields import PositiveSmallIntegerMinField, PositiveSmallIntegerRangeField, \ multipolygon_field @@ -671,6 +675,138 @@ def __str__(self): return self.betriebszeit +class Bevollmaechtigte_Bezirksschornsteinfeger(Codelist): + """ + bevollmächtigte Bezirksschornsteinfeger + """ + + bezirk = CharField( + 'Bezirk', + max_length=6, + blank=True, + null=True, + validators=[ + RegexValidator( + regex=bevollmaechtigte_bezirksschornsteinfeger_bezirk_regex, + message=bevollmaechtigte_bezirksschornsteinfeger_bezirk_message + ) + ] + ) + auswaertig = BooleanField( + ' auswärtig?' + ) + bestellungszeitraum_beginn = DateField('Beginn des Bestellungszeitraums') + bestellungszeitraum_ende = DateField('Ende des Bestellungszeitraums') + vorname = CharField( + 'Vorname', + max_length=255, + validators=personennamen_validators + ) + nachname = CharField( + 'Nachname', + max_length=255, + validators=personennamen_validators + ) + anschrift_strasse = CharField( + 'Straße', + max_length=255, + validators=standard_validators + ) + anschrift_hausnummer = CharField( + 'Hausnummer', + max_length=4, + validators=[ + RegexValidator( + regex=hausnummer_regex, + message=hausnummer_message + ) + ] + ) + anschrift_postleitzahl = CharField( + 'Postleitzahl', + max_length=5, + validators=[ + RegexValidator( + regex=postleitzahl_regex, + message=postleitzahl_message + ) + ] + ) + anschrift_ort = CharField( + 'Ort', + max_length=255, + validators=standard_validators + ) + telefon_festnetz = CharField( + 'Telefon (Festnetz)', + max_length=255, + blank=True, + null=True, + validators=[ + RegexValidator( + regex=rufnummer_regex, + message=rufnummer_message + ) + ] + ) + telefon_mobil = CharField( + 'Telefon (mobil)', + max_length=255, + blank=True, + null=True, + validators=[ + RegexValidator( + regex=rufnummer_regex, + message=rufnummer_message + ) + ] + ) + email = CharField( + 'E-Mail-Adresse', + max_length=255, + blank=True, + null=True, + validators=[ + EmailValidator( + message=email_message + ) + ] + ) + + class Meta(Codelist.Meta): + db_table = 'codelisten\".\"bevollmaechtigte_bezirksschornsteinfeger' + ordering = [ + 'nachname', + 'vorname' + ] + verbose_name = 'bevollmächtigter Bezirksschornsteinfeger' + verbose_name_plural = 'bevollmächtigte Bezirksschornsteinfeger' + + class BasemodelMeta(Codelist.BasemodelMeta): + description = 'bevollmächtigte Bezirksschornsteinfeger' + list_fields = { + 'bezirk': 'Bezirk', + 'auswaertig': 'auswärtig?', + 'bestellungszeitraum_beginn': 'Beginn des Bestellungszeitraums', + 'bestellungszeitraum_ende': 'Ende des Bestellungszeitraums', + 'vorname': 'Vorname', + 'nachname': 'Nachname', + 'anschrift': 'Anschrift', + 'telefon_festnetz': 'Telefon (Festnetz)', + 'telefon_mobil': 'Telefon (mobil)', + 'email': 'E-Mail-Adresse' + } + list_fields_with_date = ['bestellungszeitraum_beginn', 'bestellungszeitraum_ende'] + + def __str__(self): + bezirk = ' (Bezirk ' + self.bezirk + ')' if self.bezirk else '' + return self.vorname + ' ' + self.nachname + bezirk + + def address(self): + return concat_address(self.anschrift_strasse, self.anschrift_hausnummer, + self.anschrift_postleitzahl, self.anschrift_ort) + + class Bewirtschafter_Betreiber_Traeger_Eigentuemer(Codelist): """ Bewirtschafter, Betreiber, Träger, Eigentümer etc. diff --git a/datenmanagement/sql/schema.sql b/datenmanagement/sql/schema.sql index 36a88085..5acfbbfe 100644 --- a/datenmanagement/sql/schema.sql +++ b/datenmanagement/sql/schema.sql @@ -681,6 +681,30 @@ CREATE TABLE codelisten.betriebszeiten ( ); +-- +-- Name: bevollmaechtigte_bezirksschornsteinfeger; Type: TABLE; Schema: codelisten; Owner: - +-- + +CREATE TABLE codelisten.bevollmaechtigte_bezirksschornsteinfeger ( + uuid uuid DEFAULT public.uuid_generate_v4() NOT NULL, + aktualisiert date DEFAULT (now())::date NOT NULL, + erstellt date DEFAULT (now())::date NOT NULL, + auswaertig boolean NOT NULL, + bezirk character(6), + bestellungszeitraum_beginn date NOT NULL, + bestellungszeitraum_ende date NOT NULL, + vorname character varying(255) NOT NULL, + nachname character varying(255) NOT NULL, + anschrift_strasse character varying(255) NOT NULL, + anschrift_hausnummer character varying(4) NOT NULL, + anschrift_postleitzahl character(5) NOT NULL, + anschrift_ort character varying(255) NOT NULL, + telefon_festnetz character varying(255), + telefon_mobil character varying(255), + email character varying(255) +); + + -- -- Name: bewirtschafter_betreiber_traeger_eigentuemer; Type: TABLE; Schema: codelisten; Owner: - -- @@ -2964,6 +2988,24 @@ CREATE TABLE fachdaten_adressbezug.hospize_hro ( ); +-- +-- Name: kehrbezirke_hro; Type: TABLE; Schema: fachdaten_adressbezug; Owner: - +-- + +CREATE TABLE fachdaten_adressbezug.kehrbezirke_hro ( + uuid uuid DEFAULT public.uuid_generate_v4() NOT NULL, + aktualisiert date DEFAULT (now())::date NOT NULL, + erstellt date DEFAULT (now())::date NOT NULL, + id_fachsystem character varying(255), + id_zielsystem character varying(255), + aktiv boolean DEFAULT true NOT NULL, + deaktiviert date, + adresse uuid, + bevollmaechtigter_bezirksschornsteinfeger uuid NOT NULL, + vergabedatum date +); + + -- -- Name: kinder_jugendbetreuung_hro; Type: TABLE; Schema: fachdaten_adressbezug; Owner: - -- @@ -3981,6 +4023,14 @@ ALTER TABLE ONLY codelisten.betriebszeiten ADD CONSTRAINT betriebszeiten_pk PRIMARY KEY (uuid); +-- +-- Name: bevollmaechtigte_bezirksschornsteinfeger bevollmaechtigte_bezirksschornsteinfeger_pk; Type: CONSTRAINT; Schema: codelisten; Owner: - +-- + +ALTER TABLE ONLY codelisten.bevollmaechtigte_bezirksschornsteinfeger + ADD CONSTRAINT bevollmaechtigte_bezirksschornsteinfeger_pk PRIMARY KEY (uuid); + + -- -- Name: bewirtschafter_betreiber_traeger_eigentuemer bewirtschafter_betreiber_traeger_eigentuemer_bezeichnung_unique; Type: CONSTRAINT; Schema: codelisten; Owner: - -- @@ -5565,6 +5615,14 @@ ALTER TABLE ONLY fachdaten_adressbezug.hospize_hro ADD CONSTRAINT hospize_hro_pk PRIMARY KEY (uuid); +-- +-- Name: kehrbezirke_hro kehrbezirke_hro_pk; Type: CONSTRAINT; Schema: fachdaten_adressbezug; Owner: - +-- + +ALTER TABLE ONLY fachdaten_adressbezug.kehrbezirke_hro + ADD CONSTRAINT kehrbezirke_hro_pk PRIMARY KEY (uuid); + + -- -- Name: kinder_jugendbetreuung_hro kinder_jugendbetreuung_hro_pk; Type: CONSTRAINT; Schema: fachdaten_adressbezug; Owner: - -- @@ -6889,6 +6947,14 @@ ALTER TABLE ONLY fachdaten_adressbezug.hospize_hro ADD CONSTRAINT hospize_hro_traeger_fk FOREIGN KEY (traeger) REFERENCES codelisten.bewirtschafter_betreiber_traeger_eigentuemer(uuid) MATCH FULL ON UPDATE CASCADE ON DELETE RESTRICT; +-- +-- Name: kehrbezirke_hro kehrbezirke_hro_bevollmaechtigte_bezirksschornsteinfeger_fk; Type: FK CONSTRAINT; Schema: fachdaten_adressbezug; Owner: - +-- + +ALTER TABLE ONLY fachdaten_adressbezug.kehrbezirke_hro + ADD CONSTRAINT kehrbezirke_hro_bevollmaechtigte_bezirksschornsteinfeger_fk FOREIGN KEY (bevollmaechtigter_bezirksschornsteinfeger) REFERENCES codelisten.bevollmaechtigte_bezirksschornsteinfeger(uuid) MATCH FULL ON UPDATE CASCADE ON DELETE RESTRICT; + + -- -- Name: kinder_jugendbetreuung_hro kinder_jugendbetreuung_hro_traeger_fk; Type: FK CONSTRAINT; Schema: fachdaten_adressbezug; Owner: - -- diff --git a/datenmanagement/tests/test_models_codelist.py b/datenmanagement/tests/test_models_codelist.py index bdfa355d..431f5a12 100644 --- a/datenmanagement/tests/test_models_codelist.py +++ b/datenmanagement/tests/test_models_codelist.py @@ -8,13 +8,14 @@ Arten_UVP_Vorpruefungen, Arten_Wege, Auftraggeber_Baustellen, \ Ausfuehrungen_Haltestellenkataster, Befestigungsarten_Aufstellflaeche_Bus_Haltestellenkataster, \ Befestigungsarten_Warteflaeche_Haltestellenkataster, Betriebsarten, Betriebszeiten, \ - Bewirtschafter_Betreiber_Traeger_Eigentuemer, E_Anschluesse_Parkscheinautomaten, \ - Ergebnisse_UVP_Vorpruefungen, Fahrbahnwinterdienst_Strassenreinigungssatzung_HRO, \ - Fotomotive_Haltestellenkataster, Fundamenttypen_RSAG, Gebaeudebauweisen, \ - Gebaeudefunktionen, Genehmigungsbehoerden_UVP_Vorhaben, Geschlechter_Kadaverfunde, Haefen, \ - Hersteller_Poller, Kategorien_Strassen, Ladekarten_Ladestationen_Elektrofahrzeuge, Linien, \ - Mastkennzeichen_RSAG, Masttypen_RSAG, Masttypen_Haltestellenkataster, Materialien_Denksteine, \ - Materialien_Durchlaesse, Ordnungen_Fliessgewaesser, Personentitel, Quartiere, \ + Bevollmaechtigte_Bezirksschornsteinfeger, Bewirtschafter_Betreiber_Traeger_Eigentuemer, \ + E_Anschluesse_Parkscheinautomaten, Ergebnisse_UVP_Vorpruefungen, \ + Fahrbahnwinterdienst_Strassenreinigungssatzung_HRO, Fotomotive_Haltestellenkataster, \ + Fundamenttypen_RSAG, Gebaeudebauweisen, Gebaeudefunktionen, Genehmigungsbehoerden_UVP_Vorhaben, \ + Geschlechter_Kadaverfunde, Haefen, Hersteller_Poller, Kategorien_Strassen, \ + Ladekarten_Ladestationen_Elektrofahrzeuge, Linien, Mastkennzeichen_RSAG, Masttypen_RSAG, \ + Masttypen_Haltestellenkataster, Materialien_Denksteine, Materialien_Durchlaesse, \ + Ordnungen_Fliessgewaesser, Personentitel, Quartiere, \ Raeumbreiten_Strassenreinigungssatzung_HRO, Rechtsgrundlagen_UVP_Vorhaben, \ Reinigungsklassen_Strassenreinigungssatzung_HRO, \ Reinigungsrhythmen_Strassenreinigungssatzung_HRO, Schaeden_Haltestellenkataster, \ @@ -3857,6 +3858,157 @@ def test_view_deleteimmediately(self): ) +class BevollmaechtigteBezirksschornsteinfegerTest(DefaultCodelistTestCase): + """ + bevollmächtigte Bezirksschornsteinfeger + """ + + model = Bevollmaechtigte_Bezirksschornsteinfeger + create_test_subset_in_classmethod = False + attributes_values_db_initial = { + 'auswaertig': False, + 'bestellungszeitraum_beginn': VALID_DATE, + 'bestellungszeitraum_ende': VALID_DATE, + 'vorname': 'Vorname1', + 'nachname': 'Nachname1', + 'anschrift_strasse': 'Straße1', + 'anschrift_hausnummer': '123', + 'anschrift_postleitzahl': '12345', + 'anschrift_ort': 'Ort1' + } + attributes_values_db_updated = { + 'nachname': 'Nachname2' + } + attributes_values_view_initial = { + 'auswaertig': False, + 'bestellungszeitraum_beginn': VALID_DATE, + 'bestellungszeitraum_ende': VALID_DATE, + 'vorname': 'Vorname3', + 'nachname': 'Nachname3', + 'anschrift_strasse': 'Straße3', + 'anschrift_hausnummer': '345', + 'anschrift_postleitzahl': '34567', + 'anschrift_ort': 'Ort3' + } + attributes_values_view_updated = { + 'auswaertig': False, + 'bestellungszeitraum_beginn': VALID_DATE, + 'bestellungszeitraum_ende': VALID_DATE, + 'vorname': 'Vorname4', + 'nachname': 'Nachname4', + 'anschrift_strasse': 'Straße4', + 'anschrift_hausnummer': '456', + 'anschrift_postleitzahl': '45678', + 'anschrift_ort': 'Ort4' + } + attributes_values_view_invalid = { + 'nachname': INVALID_STRING + } + + def setUp(self): + self.init() + + def test_is_codelist(self): + self.generic_is_codelist_test() + + def test_create(self): + self.generic_create_test(self.model, self.attributes_values_db_initial) + + def test_update(self): + self.generic_update_test(self.model, self.attributes_values_db_updated) + + def test_delete(self): + self.generic_delete_test(self.model) + + def test_view_start(self): + self.generic_view_test( + self.model, + self.model.__name__ + '_start', + {}, + 200, + 'text/html; charset=utf-8', + START_VIEW_STRING + ) + + def test_view_list(self): + self.generic_view_test( + self.model, + self.model.__name__ + '_list', + {}, + 200, + 'text/html; charset=utf-8', + LIST_VIEW_STRING + ) + + def test_view_data(self): + self.generic_view_test( + self.model, + self.model.__name__ + '_data', + DATA_VIEW_PARAMS, + 200, + 'application/json', + str(self.test_object.pk) + ) + + def test_view_add_success(self): + self.generic_add_update_view_test( + False, + self.model, + self.attributes_values_view_initial, + 302, + 'text/html; charset=utf-8', + 1 + ) + + def test_view_add_error(self): + self.generic_add_update_view_test( + False, + self.model, + self.attributes_values_view_invalid, + 200, + 'text/html; charset=utf-8', + 0 + ) + + def test_view_change_success(self): + self.generic_add_update_view_test( + True, + self.model, + self.attributes_values_view_updated, + 302, + 'text/html; charset=utf-8', + 1 + ) + + def test_view_change_error(self): + self.generic_add_update_view_test( + True, + self.model, + self.attributes_values_view_invalid, + 200, + 'text/html; charset=utf-8', + 0 + ) + + def test_view_delete(self): + self.generic_delete_view_test( + False, + self.model, + self.attributes_values_db_initial, + 302, + 'text/html; charset=utf-8' + ) + + def test_view_deleteimmediately(self): + self.generic_delete_view_test( + True, + self.model, + self.attributes_values_db_initial, + 204, + 'text/html; charset=utf-8' + ) + + class BewirtschafterBetreiberTraegerEigentuemerTest(DefaultCodelistTestCase): """ Bewirtschafter, Betreiber, Träger, Eigentümer etc. diff --git a/datenmanagement/views/views_list_map.py b/datenmanagement/views/views_list_map.py index b63db8a0..e16e2cf6 100644 --- a/datenmanagement/views/views_list_map.py +++ b/datenmanagement/views/views_list_map.py @@ -66,97 +66,105 @@ def prepare_results(self, qs): item_data.append( '') for column in self.columns: - value = getattr(item, column) - data = None - # handle non-empty fields only! - if value is not None: - # format foreign keys - if ( - self.columns_with_foreign_key - and column in self.columns_with_foreign_key - and self.fields_with_foreign_key_to_linkify - and column in self.fields_with_foreign_key_to_linkify - ): - foreign_model = value._meta.label - foreign_model_primary_key = value._meta.pk.name - foreign_model_title = self.columns.get(column) - foreign_model_attribute_for_text = self.columns_with_foreign_key.get(column) - data = '' + str( - getattr(value, foreign_model_attribute_for_text)) + '' - # format numbers - elif self.columns_with_number and column in self.columns_with_number: - if isinstance(value, Decimal) or match(r"^[0-9]+\.[0-9]+$", str(value)): - data = localize_number(Decimal(str(value))) - else: - data = value - # format dates - elif self.columns_with_date and column in self.columns_with_date: - data = datetime.strptime(str(value), '%Y-%m-%d').strftime('%d.%m.%Y') - # format datetimes - elif self.columns_with_datetime and column in self.columns_with_datetime: - datetimestamp_str = sub(r'([+-][0-9]{2}):', '\\1', str(value)) - datetimestamp = datetime.strptime(datetimestamp_str, '%Y-%m-%d %H:%M:%S%z').\ - replace(tzinfo=timezone.utc).astimezone(ZoneInfo(settings.TIME_ZONE)) - datetimestamp_str = datetimestamp.strftime('%d.%m.%Y, %H:%M:%S Uhr') - data = datetimestamp_str - # handle highlight flags - elif self.column_as_highlight_flag and column == self.column_as_highlight_flag: - data = '
ja
' - # handle photo files - elif column == 'foto': - try: - data = ('') - if self.thumbs: - data += '' + # all columns except address strings! + if not column == 'anschrift': + value = getattr(item, column) + data = None + # handle non-empty fields only! + if value is not None: + # format foreign keys + if ( + self.columns_with_foreign_key + and column in self.columns_with_foreign_key + and self.fields_with_foreign_key_to_linkify + and column in self.fields_with_foreign_key_to_linkify + ): + foreign_model = value._meta.label + foreign_model_primary_key = value._meta.pk.name + foreign_model_title = self.columns.get(column) + foreign_model_attribute_for_text = self.columns_with_foreign_key.get(column) + data = '' + str( + getattr(value, foreign_model_attribute_for_text)) + '' + # format numbers + elif self.columns_with_number and column in self.columns_with_number: + if isinstance(value, Decimal) or match(r"^[0-9]+\.[0-9]+$", str(value)): + data = localize_number(Decimal(str(value))) else: - data += '' - data += '' - except ValueError: - pass - # handle PDF files - elif column == 'dokument' or column == 'pdf': - try: - data = 'Link zum ' + ( - ('PDF' if column == 'pdf' else 'Dokument')) + '' - except ValueError: - pass - # format Boolean ``True`` - elif value is True: - data = 'ja' - # format Boolean ``False`` - elif value is False: - data = 'nein' - # format lists - elif type(value) in [list, tuple]: - data = ', '.join(map(str, value)) - # format external links - elif isinstance(value, str) and value.startswith('http'): - data = ('' + - value + '') - # format colors - elif isinstance(value, str) and match(r"^#[a-f0-9]{6}$", value, IGNORECASE): - data = 'ja
' + # handle photo files + elif column == 'foto': + try: + data = ('') + if self.thumbs: + data += '' + else: + data += '' + data += '' + except ValueError: + pass + # handle PDF files + elif column == 'dokument' or column == 'pdf': + try: + data = 'Link zum ' + ( + ('PDF' if column == 'pdf' else 'Dokument')) + '' + except ValueError: + pass + # format Boolean ``True`` + elif value is True: + data = 'ja' + # format Boolean ``False`` + elif value is False: + data = 'nein' + # format lists + elif type(value) in [list, tuple]: + data = ', '.join(map(str, value)) + # format external links + elif isinstance(value, str) and value.startswith('http'): + data = ('' + + value + '') + # format colors + elif isinstance(value, str) and match(r"^#[a-f0-9]{6}$", value, IGNORECASE): + data = '