Skip to content

Commit 49a5762

Browse files
committed
Snippet processing with Power-BI reports to be authd
1 parent 8489043 commit 49a5762

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

api/drf_views.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,16 @@ def get_queryset(self):
297297
user = getattr(self.request, "user", None)
298298

299299
if not user or not user.is_authenticated:
300-
snip_qs = RegionSnippet.objects.filter(visibility=VisibilityChoices.PUBLIC)
300+
# Guests: only PUBLIC and exclude backend-gated power_bi embeds entirely
301+
snip_qs = RegionSnippet.objects.filter(visibility=VisibilityChoices.PUBLIC).exclude(
302+
snippet__contains='data-snippet-type="power_bi"'
303+
)
301304
else:
302305
profile = getattr(user, "profile", None)
303306
if profile and profile.limit_access_to_guest:
304-
snip_qs = RegionSnippet.objects.filter(visibility=VisibilityChoices.PUBLIC)
307+
snip_qs = RegionSnippet.objects.filter(visibility=VisibilityChoices.PUBLIC).exclude(
308+
snippet__contains='data-snippet-type="power_bi"'
309+
)
305310
elif is_user_ifrc(user):
306311
snip_qs = RegionSnippet.objects.all()
307312
else:
@@ -318,6 +323,8 @@ def get_queryset(self):
318323
snip_qs = snip_qs.exclude(
319324
Q(visibility=VisibilityChoices.IFRC_NS) & ~Q(region_id__in=allowed_region_ids_for_ifrc_ns)
320325
)
326+
# Exclude power_bi embedded snippets if marked auth-required and user is not IFRC
327+
snip_qs = snip_qs.exclude(snippet__contains='data-snippet-type="power_bi"')
321328

322329
return self.queryset.prefetch_related(models.Prefetch("snippets", queryset=snip_qs))
323330

@@ -679,6 +686,16 @@ def get_serializer_class(self):
679686
return RegionSnippetTableauSerializer
680687
return RegionSnippetSerializer
681688

689+
def get_queryset(self):
690+
qs = super().get_queryset()
691+
user = getattr(self.request, "user", None)
692+
if not user or not user.is_authenticated or (getattr(user, "profile", None) and user.profile.limit_access_to_guest):
693+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
694+
# Non-IFRC auth users: still exclude power_bi when present and auth_required
695+
if not is_user_ifrc(user):
696+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
697+
return qs
698+
682699

683700
class CountrySnippetViewset(ReadOnlyVisibilityViewset):
684701
authentication_classes = (TokenAuthentication,)
@@ -691,6 +708,15 @@ def get_serializer_class(self):
691708
return CountrySnippetTableauSerializer
692709
return CountrySnippetSerializer
693710

711+
def get_queryset(self):
712+
qs = super().get_queryset()
713+
user = getattr(self.request, "user", None)
714+
if not user or not user.is_authenticated or (getattr(user, "profile", None) and user.profile.limit_access_to_guest):
715+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
716+
if not is_user_ifrc(user):
717+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
718+
return qs
719+
694720

695721
class DistrictViewset(viewsets.ReadOnlyModelViewSet):
696722
queryset = District.objects.select_related("country").filter(country__is_deprecated=False).filter(is_deprecated=False)
@@ -888,6 +914,15 @@ class EventSnippetViewset(ReadOnlyVisibilityViewset):
888914
visibility_model_class = Snippet
889915
ordering_fields = "__all__"
890916

917+
def get_queryset(self):
918+
qs = super().get_queryset()
919+
user = getattr(self.request, "user", None)
920+
if not user or not user.is_authenticated or (getattr(user, "profile", None) and user.profile.limit_access_to_guest):
921+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
922+
if not is_user_ifrc(user):
923+
return qs.exclude(snippet__contains='data-snippet-type="power_bi"')
924+
return qs
925+
891926

892927
class SituationReportTypeViewset(viewsets.ReadOnlyModelViewSet):
893928
queryset = SituationReportType.objects.all()

api/serializers.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
# from api.utils import pdf_exporter
1414
from api.tasks import generate_url
15-
from api.utils import CountryValidator, RegionValidator
15+
from api.utils import CountryValidator, RegionValidator, parse_snippet_embed
1616
from deployments.models import EmergencyProject, Personnel, PersonnelDeployment
1717
from dref.models import Dref, DrefFinalReport, DrefOperationalUpdate
1818
from lang.models import String
@@ -485,6 +485,7 @@ class Meta:
485485

486486
class RegionSnippetSerializer(ModelSerializer):
487487
visibility_display = serializers.CharField(source="get_visibility_display", read_only=True)
488+
embed = serializers.SerializerMethodField()
488489

489490
class Meta:
490491
model = RegionSnippet
@@ -494,13 +495,18 @@ class Meta:
494495
"image",
495496
"visibility",
496497
"visibility_display",
498+
"embed",
497499
"id",
498500
)
499501

500502
def validate_image(self, image):
501503
validate_file_type(image)
502504
return image
503505

506+
@staticmethod
507+
def get_embed(obj):
508+
return parse_snippet_embed(obj.snippet)
509+
504510

505511
class RegionEmergencySnippetSerializer(ModelSerializer):
506512
class Meta:
@@ -553,6 +559,7 @@ class Meta:
553559

554560
class CountrySnippetSerializer(ModelSerializer):
555561
visibility_display = serializers.CharField(source="get_visibility_display", read_only=True)
562+
embed = serializers.SerializerMethodField()
556563

557564
class Meta:
558565
model = CountrySnippet
@@ -562,13 +569,18 @@ class Meta:
562569
"image",
563570
"visibility",
564571
"visibility_display",
572+
"embed",
565573
"id",
566574
)
567575

568576
def validate_image(self, image):
569577
validate_file_type(image)
570578
return image
571579

580+
@staticmethod
581+
def get_embed(obj):
582+
return parse_snippet_embed(obj.snippet)
583+
572584

573585
class RegionLinkSerializer(ModelSerializer):
574586
class Meta:
@@ -883,6 +895,7 @@ class SnippetSerializer(ModelSerializer):
883895
visibility_display = serializers.CharField(source="get_visibility_display", read_only=True)
884896
position_display = serializers.CharField(source="get_position_display", read_only=True)
885897
tab_display = serializers.CharField(source="get_tab_display", read_only=True)
898+
embed = serializers.SerializerMethodField()
886899

887900
class Meta:
888901
model = Snippet
@@ -897,12 +910,17 @@ class Meta:
897910
"position_display",
898911
"tab",
899912
"tab_display",
913+
"embed",
900914
)
901915

902916
def validate_image(self, image):
903917
validate_file_type(image)
904918
return image
905919

920+
@staticmethod
921+
def get_embed(obj):
922+
return parse_snippet_embed(obj.snippet)
923+
906924

907925
class EventContactSerializer(ModelSerializer):
908926
class Meta:

api/utils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,57 @@ class RegionValidator(TypedDict):
156156
class CountryValidator(TypedDict):
157157
country: int
158158
local_unit_types: list[int]
159+
160+
161+
# --- Snippet embed helpers ---
162+
def parse_snippet_embed(html: str) -> Optional[dict]:
163+
"""
164+
Parse supported embed metadata from an HTML snippet.
165+
166+
Convention: use a lightweight container tag with data-attributes, e.g.
167+
<div class="embed-power-bi" data-snippet-type="power_bi" data-report-id="<GUID>" data-auth-required="true"></div>
168+
169+
Returns a dict like {"type": "power_bi", "report_id": "...", "auth_required": True}
170+
if detected; otherwise None.
171+
172+
Notes:
173+
- No script parsing; explicit data- attributes only.
174+
- auth_required defaults to True when missing.
175+
"""
176+
if not html:
177+
return None
178+
179+
try:
180+
# Simple, safe regex extraction without executing or parsing scripts
181+
import re
182+
183+
# Look for a tag with data-snippet-type="power_bi"
184+
type_match = re.search(r'data-snippet-type\s*=\s*"(power_bi)"', html, re.IGNORECASE)
185+
if not type_match:
186+
return None
187+
188+
# Extract report-id or embed-url
189+
report_id_match = re.search(r'data-report-id\s*=\s*"([^"]+)"', html)
190+
embed_url_match = re.search(r'data-embed-url\s*=\s*"([^"]+)"', html)
191+
192+
report_id = report_id_match.group(1) if report_id_match else None
193+
embed_url = embed_url_match.group(1) if embed_url_match else None
194+
195+
# auth-required (defaults true)
196+
auth_match = re.search(r'data-auth-required\s*=\s*"?(true|false)"?', html, re.IGNORECASE)
197+
auth_required = True
198+
if auth_match:
199+
auth_required = auth_match.group(1).lower() == "true"
200+
201+
result = {
202+
"type": "power_bi",
203+
"auth_required": auth_required,
204+
}
205+
if report_id:
206+
result["report_id"] = report_id
207+
if embed_url:
208+
result["embed_url"] = embed_url
209+
return result
210+
except Exception:
211+
# Be resilient – any parsing issue just returns None
212+
return None

0 commit comments

Comments
 (0)