From 8cada7402f984c4b608c38d2b98254aa61382444 Mon Sep 17 00:00:00 2001 From: Erick Otenyo Date: Fri, 31 May 2024 12:53:29 +0300 Subject: [PATCH 1/5] Rename uuid field to guid instead of identifier --- capeditor/models.py | 15 +++++++++------ capeditor/serializers.py | 6 ++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/capeditor/models.py b/capeditor/models.py index b465951..27a0f88 100644 --- a/capeditor/models.py +++ b/capeditor/models.py @@ -106,7 +106,10 @@ def clean(self): for reference in references: ref_alert_page = reference.value.get("ref_alert").specific if ref_alert_page: - alerts_ids.append(ref_alert_page.identifier) + if hasattr(ref_alert_page, 'identifier'): + alerts_ids.append(ref_alert_page.identifier) + else: + alerts_ids.append(ref_alert_page.id) # check if the same alert is selected more than once if len(alerts_ids) != len(set(alerts_ids)): @@ -118,7 +121,7 @@ def clean(self): class AbstractCapAlertPage(Page): base_form_class = CapAlertPageForm - exclude_fields_in_copy = ["identifier"] + exclude_fields_in_copy = ["guid"] STATUS_CHOICES = ( ("Draft", _("Draft - A preliminary template or draft, not actionable in its current form")), @@ -146,9 +149,8 @@ class AbstractCapAlertPage(Page): ('Private', _("Private - For dissemination only to specified addresses" " as in the addresses field in the alert")), ) - - identifier = models.UUIDField(default=uuid.uuid4, editable=False, verbose_name=_("Identifier"), - help_text=_("Unique ID. Auto generated on creation."), unique=True) + guid = models.UUIDField(default=uuid.uuid4, editable=False, verbose_name=_("Id"), + help_text=_("Unique ID. Auto generated on creation."), unique=True) sender = models.CharField(max_length=255, verbose_name=_("Sender"), default=get_default_sender, help_text=_("Identifies the originator of an alert. " "For example the website address of the institution")) @@ -292,7 +294,8 @@ def infos(self): "expires": expires, "expired": expired, "properties": { - "id": self.identifier, + "id": self.guid, + "identifier": self.identifier if hasattr(self, "identifier") else self.guid, "event": event, "event_type": info.value.get('event'), "headline": info.value.get("headline"), diff --git a/capeditor/serializers.py b/capeditor/serializers.py index c5c8dca..4ecb5f2 100644 --- a/capeditor/serializers.py +++ b/capeditor/serializers.py @@ -27,6 +27,7 @@ class AlertSerializer(serializers.ModelSerializer): references = serializers.SerializerMethodField() info = serializers.SerializerMethodField() incidents = serializers.SerializerMethodField() + identifier = serializers.SerializerMethodField() class Meta: fields = [ @@ -46,6 +47,11 @@ class Meta: "info", ] + def get_identifier(self, obj): + if hasattr(obj, 'identifier'): + return obj.identifier + return obj.guid + def get_info(self, obj): request = self.context.get("request") info_values = [] From 4b79138e8ebc5dcff1c0ae209fc0985e03f4a28a Mon Sep 17 00:00:00 2001 From: Erick Otenyo Date: Fri, 31 May 2024 12:54:01 +0300 Subject: [PATCH 2/5] Add wmo_oid field for use in generating CAP message identifier --- capeditor/cap_settings.py | 7 +++++++ .../migrations/0006_capsetting_wmo_oid.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 capeditor/migrations/0006_capsetting_wmo_oid.py diff --git a/capeditor/cap_settings.py b/capeditor/cap_settings.py index 4b6abf3..285cf47 100644 --- a/capeditor/cap_settings.py +++ b/capeditor/cap_settings.py @@ -25,6 +25,12 @@ class CapSetting(BaseSiteSetting, ClusterableModel): help_text=_("Email of the sending institution")) sender_name = models.CharField(max_length=255, blank=True, null=True, verbose_name=_("CAP Sender Name"), help_text=_("Name of the sending institution")) + wmo_oid = models.CharField(max_length=255, blank=True, null=True, + verbose_name=_("WMO Register of Alerting Authorities OID"), + help_text=_("WMO Register of Alerting Authorities " + "Object Identifier (OID) of the sending institution. " + "This will be used to generate CAP messages identifiers")) + logo = models.ForeignKey("wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", verbose_name=_("Logo of the sending institution")) @@ -45,6 +51,7 @@ class Meta: ObjectList([ FieldPanel("sender_name"), FieldPanel("sender"), + FieldPanel("wmo_oid"), FieldPanel("logo"), FieldPanel("contacts"), ], heading=_("Sender Details")), diff --git a/capeditor/migrations/0006_capsetting_wmo_oid.py b/capeditor/migrations/0006_capsetting_wmo_oid.py new file mode 100644 index 0000000..cd4acb6 --- /dev/null +++ b/capeditor/migrations/0006_capsetting_wmo_oid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-05-30 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('capeditor', '0005_alter_capsetting_sender'), + ] + + operations = [ + migrations.AddField( + model_name='capsetting', + name='wmo_oid', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='WMO Register of Alerting Authorities OID'), + ), + ] From fd29b94527b722bfdfee68ac354fc3004a3f234e Mon Sep 17 00:00:00 2001 From: Erick Otenyo Date: Fri, 31 May 2024 12:54:56 +0300 Subject: [PATCH 3/5] Remove Admin Boundary from options for creating alert areas --- README.md | 12 +++------ capeditor/blocks.py | 66 +-------------------------------------------- 2 files changed, 5 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 7cbd4b9..bc811a8 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,9 @@ http://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2-os.html - Inbuilt CAP validation. The page will not save if you have not input the required data according to CAP standard - User-friendly alert area map tool that allows multiple ways of constructing alert geographic areas, while keeping the interface simple - - Select country official administrative boundaries for different levels (Admin 1, Admin 2, Admin 3), with in-built - simplification of complex boundaries - Draw a polygon - Draw a circle + - Selecting predefined areas that you create beforehand for common alert areas - Use Geocode key values - Inbuilt publishing workflow using Wagtail's powerful page model, with automated emails to composers and approvers - Collaborate with team members using inbuilt comments (similar to how you could do in Word) with automated @@ -188,12 +187,7 @@ area contains the following elements: - Altitude (altitude), - Ceiling (ceiling) -The Alert area input has 4 selector options: - -- Admin Boundary (area is picked from predefined boundaries). To use this option, ensure that admin boundaries are - initially loaded. Refer to [Setting up boundaries](#setting-up-boundaries) section. - -![Alert Area boundary](images/alert_sections/alert_area_boundary.png "Alert Area Boundary") +The Alert area input has multiple selector options: - Polygon (drawing a polygon) @@ -203,6 +197,8 @@ The Alert area input has 4 selector options: ![Alert Area circle](images/alert_sections/alert_area_circle.png "Alert Area Circle") +- Predefined Area (selecting a predefined area that you create beforehand for common alert areas) + - Geocode (specifying area geocode name and value). Using this option presumes knowledge of the coding system ![Alert Area geocode](images/alert_sections/alert_area_geocode.png "Alert Area Geocode") diff --git a/capeditor/blocks.py b/capeditor/blocks.py index d46e2e5..3a72f98 100644 --- a/capeditor/blocks.py +++ b/capeditor/blocks.py @@ -160,69 +160,6 @@ class AlertResponseType(blocks.StructBlock): "target audience")) -class AlertAreaBoundaryStructValue(StructValue): - @cached_property - def area(self): - geom_geojson_str = self.get("boundary") - geom_geojson_dict = json.loads(geom_geojson_str) - geom_shape = shape(geom_geojson_dict) - - polygons = [] - - if isinstance(geom_shape, Polygon): - polygons.append(geom_shape) - else: - polygons = list(geom_shape.geoms) - - polygons_data = [] - for polygon in polygons: - coords = " ".join(["{},{}".format(y, x) for x, y in list(polygon.exterior.reverse().coords)]) - polygons_data.append(coords) - - area_data = { - "areaDesc": self.get("areaDesc"), - "polygons": polygons_data - } - - if self.get("altitude"): - area_data.update({"altitude": self.get("altitude")}) - if self.get("ceiling"): - area_data.update({"ceiling": self.get("ceiling")}) - - return area_data - - @cached_property - def geojson(self): - polygon = self.get("boundary") - return json.loads(polygon) - - -class AlertAreaBoundaryBlock(blocks.StructBlock): - class Meta: - value_class = AlertAreaBoundaryStructValue - - ADMIN_LEVEL_CHOICES = ( - (0, _("Level 0")), - (1, _("Level 1")), - (2, _("Level 2")), - (3, _("Level 3")) - ) - - areaDesc = blocks.TextBlock(label=_("Affected areas / Regions"), - help_text=_("The text describing the affected area of the alert message")) - admin_level = blocks.ChoiceBlock(choices=ADMIN_LEVEL_CHOICES, default=1, label=_("Administrative Level")) - boundary = BoundaryFieldBlock(label=_("Boundary"), - help_text=_("The paired values of points defining a polygon that delineates " - "the affected area of the alert message")) - - altitude = blocks.CharBlock(max_length=100, required=False, label=_("Altitude"), - help_text=_("The specific or minimum altitude of the affected " - "area of the alert message")) - ceiling = blocks.CharBlock(max_length=100, required=False, label=_("Ceiling"), - help_text=_("The maximum altitude of the affected area of the alert message." - "MUST NOT be used except in combination with the altitude element. ")) - - class AlertAreaPolygonStructValue(StructValue): @cached_property def area(self): @@ -679,12 +616,11 @@ class AlertInfo(blocks.StructBlock): audience = blocks.CharBlock(max_length=255, required=False, label=_("Audience"), help_text=AUDIENCE_HELP_TEXT) area = blocks.StreamBlock([ - ("boundary_block", AlertAreaBoundaryBlock(label=_("Admin Boundary"))), ("polygon_block", AlertAreaPolygonBlock(label=_("Draw Polygon"))), ("circle_block", AlertAreaCircleBlock(label=_("Circle"))), ("geocode_block", AlertAreaGeocodeBlock(label=_("Geocode"))), ("predefined_block", AlertAreaPredefined(label=_("Predefined Area"))), - ], label=_("Alert Area"), help_text=_("Admin Boundary, Polygon, Circle or Geocode")) + ], label=_("Alert Area"), help_text=_("Polygon, Circle, Predefined area or Geocode")) resource = blocks.StreamBlock([ ("file_resource", FileResource()), From 8fc2ad438d275819baa59ae035f042634e40b84f Mon Sep 17 00:00:00 2001 From: Erick Otenyo Date: Fri, 31 May 2024 12:55:29 +0300 Subject: [PATCH 4/5] updates --- sandbox/home/models.py | 4 ++-- sandbox/home/urls.py | 2 +- sandbox/home/views.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sandbox/home/models.py b/sandbox/home/models.py index b130e92..458757a 100644 --- a/sandbox/home/models.py +++ b/sandbox/home/models.py @@ -51,7 +51,7 @@ class CapAlertPage(AbstractCapAlertPage): parent_page_type = ["home.HomePage"] subpage_types = [] - exclude_fields_in_copy = ["identifier", ] + exclude_fields_in_copy = ["guid", ] """An implementation of MetadataMixin for Wagtail pages.""" search_image = models.ForeignKey( @@ -92,7 +92,7 @@ class Meta: @cached_property def xml_link(self): - return reverse("cap_alert_detail", args=(self.identifier,)) + return reverse("cap_alert_detail", args=(self.guid,)) def on_publish_cap_alert(sender, **kwargs): diff --git a/sandbox/home/urls.py b/sandbox/home/urls.py index 8f4d516..c684666 100644 --- a/sandbox/home/urls.py +++ b/sandbox/home/urls.py @@ -4,5 +4,5 @@ urlpatterns = [ path('api/cap/feed.xml', AlertList.as_view(), name="cap_alert_feed"), - path("api/cap/.xml", AlertDetail.as_view(), name="cap_alert_detail"), + path("api/cap/.xml", AlertDetail.as_view(), name="cap_alert_detail"), ] diff --git a/sandbox/home/views.py b/sandbox/home/views.py index 125a9ff..9115aa0 100644 --- a/sandbox/home/views.py +++ b/sandbox/home/views.py @@ -20,4 +20,4 @@ class AlertDetail(generics.RetrieveAPIView): renderer_classes = (CapXMLRenderer,) queryset = CapAlertPage.objects.live() - lookup_field = "identifier" + lookup_field = "guid" From 7386803736a0f73f71c21d2f3bd71485ff43e6ec Mon Sep 17 00:00:00 2001 From: Erick Otenyo Date: Fri, 31 May 2024 12:57:53 +0300 Subject: [PATCH 5/5] Bump version --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 62d2a41..ff295b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = capeditor -version = 0.5.7 +version = 0.5.8 description = Wagtail based CAP composer long_description = file:README.md long_description_content_type = text/markdown