Skip to content

Commit

Permalink
feb-r02 (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
mainlyIt authored Feb 10, 2025
1 parent 5045546 commit f764a32
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 67 deletions.
5 changes: 2 additions & 3 deletions src/components/ObservationDetailsComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,8 @@ export default {
const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
const hoursDiff = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
if (daysDiff < 5) {
let remainingDays = 5 - daysDiff;
if (daysDiff < 10) {
let remainingDays = 10 - daysDiff;
let remainingHours = 0;
if (hoursDiff > 0) {
Expand All @@ -603,7 +603,6 @@ export default {
return null;
}
});
const closeDetails = () => {
emit('closeDetails');
vespaStore.isDetailsPaneOpen = false;
Expand Down
9 changes: 6 additions & 3 deletions vespadb/observations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.contrib.gis import admin as gis_admin
from django.core.mail import send_mail
from django.db.models import Q
from django.contrib.gis.geos import GEOSGeometry, Point
from django.db.models.query import QuerySet
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect, render
Expand Down Expand Up @@ -182,13 +183,14 @@ def save_model(self, request: HttpRequest, obj: Observation, form: Any, change:

# Auto-edit based on location
if obj.location:
if not isinstance(obj.location, Point):
obj.location = GEOSGeometry(obj.location, srid=4326)
point = obj.location.transform(4326, clone=True)
long, lat = point.x, point.y
obj.anb = check_if_point_in_anb_area(long, lat)
municipality = get_municipality_from_coordinates(long, lat)
obj.municipality = municipality
obj.province = municipality.province if municipality else None

if not obj.source:
obj.source = "Waarnemingen.be"

Expand Down Expand Up @@ -273,8 +275,9 @@ def bulk_import_view(self, request: HttpRequest) -> HttpResponse:
# Initialize the viewset with the new request
viewset = ObservationsViewSet.as_view({"post": "bulk_import"})
response = viewset(api_request)
if response.status_code == 201: # noqa: PLR2004
self.message_user(request, "Observations imported successfully.")
if response.status_code == 201:
imported_ids = response.data.get("observation_ids", [])
self.message_user(request, f"Observations imported successfully. IDs: {imported_ids}")
else:
self.message_user(request, f"Failed to import observations: {response.data}", level="error")
return redirect("admin:observations_observation_changelist")
Expand Down
54 changes: 37 additions & 17 deletions vespadb/observations/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pytz import timezone
from rest_framework import serializers
from rest_framework.request import Request
from rest_framework_gis.fields import GeometryField

from vespadb.observations.helpers import parse_and_convert_to_cet, parse_and_convert_to_utc
from vespadb.observations.models import EradicationResultEnum, Municipality, Observation, Province, Export
Expand Down Expand Up @@ -121,6 +122,7 @@ class ObservationSerializer(serializers.ModelSerializer):
eradication_date = serializers.DateField(
required=False, allow_null=True, format="%Y-%m-%d", input_formats=["%Y-%m-%d"]
)
location = GeometryField(required=False, allow_null=True)

class Meta:
"""Meta class for the ObservationSerializer."""
Expand Down Expand Up @@ -154,23 +156,31 @@ def get_created_by_first_name(self, obj: Observation) -> str | None:

def to_representation(self, instance: Observation) -> dict[str, Any]: # noqa: C901
"""Dynamically filter fields based on user authentication status."""

# Ensure municipality and province are correctly assigned
if not instance.municipality and instance.location:
long = instance.location.x
lat = instance.location.y
long, lat = instance.location.x, instance.location.y
instance.municipality = get_municipality_from_coordinates(long, lat)
if instance.municipality:
instance.province = instance.municipality.province
instance.save(update_fields=["municipality", "province"])

# Get base serialized data
data: dict[str, Any] = super().to_representation(instance)
observation_datetime = data.get('observation_datetime')

# Ensure created_by_first_name is properly set
if 'created_by' not in data or data['created_by'] is None:
data['created_by_first_name'] = None

# Convert observation_datetime to full datetime if only a date is provided
observation_datetime = data.get('observation_datetime')
if isinstance(observation_datetime, date) and not isinstance(observation_datetime, datetime):
data['observation_datetime'] = datetime.combine(observation_datetime, datetime.min.time())

data.pop("wn_admin_notes", None)

# Remove fields that should never be exposed
data.pop("wn_admin_notes", None)

# Convert datetime fields to ISO format
datetime_fields = [
"created_datetime",
"modified_datetime",
Expand All @@ -180,9 +190,11 @@ def to_representation(self, instance: Observation) -> dict[str, Any]: # noqa: C
"observation_datetime",
]
date_fields = ["eradication_date"]

for field in datetime_fields:
if data.get(field):
data[field] = parse_and_convert_to_cet(data[field]).isoformat()

for field in date_fields:
if data.get(field):
date_str: str = data[field]
Expand All @@ -193,9 +205,13 @@ def to_representation(self, instance: Observation) -> dict[str, Any]: # noqa: C
parsed_date = datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=UTC)
data[field] = parsed_date.strftime("%Y-%m-%d")

request: Request = self.context.get("request")
# Ensure municipality name is included
data["municipality_name"] = self.get_municipality_name(instance)

# Retrieve request context safely
request: Request | None = self.context.get("request", None)

# Determine accessible fields based on authentication status
if request and request.user.is_authenticated:
user: VespaUser = request.user
permission_level = user.get_permission_level()
Expand All @@ -204,19 +220,18 @@ def to_representation(self, instance: Observation) -> dict[str, Any]: # noqa: C
instance.municipality and instance.municipality.id in user_municipality_ids
)

# Voor gebruikers zonder toegang tot specifieke gemeenten
if permission_level == "logged_in_without_municipality":
return {field: data[field] for field in public_read_fields if field in data}

# Voor gebruikers met toegang tot specifieke gemeenten, extra gegevens tonen indien binnen hun gemeenten
if is_inside_user_municipality or request.user.is_superuser:
return {field: data[field] for field in user_read_fields if field in data}

# Voor observaties buiten de gemeenten van de gebruiker, beperk tot publieke velden
return {field: data[field] for field in public_read_fields if field in data}
allowed_fields = public_read_fields
elif is_inside_user_municipality or user.is_superuser:
allowed_fields = user_read_fields
else:
allowed_fields = public_read_fields
else:
# Fix: Ensure unauthenticated users always get public_read_fields
allowed_fields = public_read_fields

# Voor niet-ingelogde gebruikers, retourneer enkel de publieke velden
return {field: data[field] for field in public_read_fields if field in data}
# Return only the allowed fields
return {field: data[field] for field in allowed_fields if field in data}

def validate_reserved_by(self, value: VespaUser) -> VespaUser:
"""Validate that the user does not exceed the maximum number of allowed reservations and has permission to reserve in the specified municipality."""
Expand Down Expand Up @@ -386,6 +401,9 @@ def to_internal_value(self, data: dict[str, Any]) -> dict[str, Any]:

def validate_location(self, value: Any) -> Point:
"""Validate the input location data. Handle different formats of location data."""
if value is None:
return None

if isinstance(value, Point):
return value

Expand All @@ -394,12 +412,14 @@ def validate_location(self, value: Any) -> Point:
return GEOSGeometry(value, srid=4326)
except (ValueError, TypeError) as e:
raise serializers.ValidationError("Invalid WKT format for location.") from e

elif isinstance(value, dict):
latitude = value.get("latitude")
longitude = value.get("longitude")
if latitude is None or longitude is None:
raise serializers.ValidationError("Missing or invalid location data")
return Point(float(longitude), float(latitude), srid=4326)

raise serializers.ValidationError("Invalid location data type")


Expand Down
29 changes: 27 additions & 2 deletions vespadb/observations/tasks/export_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def prepare_row_data(
elif field in ["created_datetime", "modified_datetime", "observation_datetime"]:
datetime_val = getattr(observation, field, None)
if datetime_val:
# Remove microseconds and tzinfo for export consistency
datetime_val = datetime_val.replace(microsecond=0, tzinfo=None)
row_data.append(datetime_val.strftime("%Y-%m-%dT%H:%M:%SZ"))
else:
Expand All @@ -77,8 +78,32 @@ def prepare_row_data(
value = getattr(observation, "eradication_result", "")
row_data.append(str(value) if value is not None else "")
elif field == "images":
value = getattr(observation, "images", "")
row_data.append(str(value) if value is not None else "")
value = getattr(observation, "images", [])
if isinstance(value, list):
if not value:
row_data.append("")
elif len(value) == 1:
# One image → export URL without quotes
row_data.append(value[0])
else:
# Multiple images → join with commas (no spaces) and enclose in double quotes
joined = ",".join(value)
row_data.append(f'"{joined}"')
else:
# In case the field is stored as a string that looks like a list
s = str(value)
if s.startswith("[") and s.endswith("]"):
s = s[1:-1].strip()
parts = [part.strip().strip("'").strip('"') for part in s.split(",") if part.strip()]
if not parts:
row_data.append("")
elif len(parts) == 1:
row_data.append(parts[0])
else:
joined = ",".join(parts)
row_data.append(f'"{joined}"')
else:
row_data.append(s)
elif field == "notes":
value = getattr(observation, "notes", "")
row_data.append(str(value) if value is not None else "")
Expand Down
Loading

0 comments on commit f764a32

Please sign in to comment.