Skip to content

Commit

Permalink
Merge coordinates serializer and improve api type safety
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel-Therrien-Beslogic committed Nov 28, 2024
1 parent 1238296 commit b6ce80f
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 242 deletions.
35 changes: 14 additions & 21 deletions canopeum_backend/canopeum_backend/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,20 @@ class Meta:


class CoordinatesSerializer(serializers.ModelSerializer[Coordinate]):
# The serializer doesn't understand these are decimal (float), not string
dd_latitude = serializers.SerializerMethodField()
dd_longitude = serializers.SerializerMethodField()

class Meta:
model = Coordinate
fields = "__all__"

def get_dd_latitude(self, obj: Coordinate) -> Decimal | None:
return obj.dd_latitude

def get_dd_longitude(self, obj: Coordinate) -> Decimal | None:
return obj.dd_longitude


class WidgetSerializer(serializers.ModelSerializer[Widget]):
class Meta:
Expand Down Expand Up @@ -354,6 +364,7 @@ def update(self, instance, validated_data: Mapping[str, Any]):

class SiteSocialSerializer(serializers.ModelSerializer[Site]):
site_type = SiteTypeSerializer()
coordinate = CoordinatesSerializer()
contact = ContactSerializer()
announcement = AnnouncementSerializer()
widget = serializers.SerializerMethodField()
Expand All @@ -367,6 +378,7 @@ class Meta:
"name",
"is_public",
"site_type",
"coordinate",
"image",
"description",
"contact",
Expand Down Expand Up @@ -631,33 +643,14 @@ def get_batches(self, obj):
return BatchDetailSerializer(batches, many=True).data


class CoordinatesMapSerializer(serializers.ModelSerializer[Coordinate]):
latitude = serializers.SerializerMethodField()
longitude = serializers.SerializerMethodField()

class Meta:
model = Coordinate
fields = ("latitude", "longitude", "address")

def get_latitude(self, obj: Coordinate) -> Decimal | None:
return obj.dd_latitude

def get_longitude(self, obj: Coordinate) -> Decimal | None:
return obj.dd_longitude


class SiteMapSerializer(serializers.ModelSerializer[Site]):
site_type = SiteTypeSerializer()
coordinates = serializers.SerializerMethodField()
coordinate = CoordinatesSerializer()
image = AssetSerializer()

class Meta:
model = Site
fields = ("id", "name", "site_type", "coordinates", "image")

@extend_schema_field(CoordinatesMapSerializer)
def get_coordinates(self, obj):
return CoordinatesMapSerializer(obj.coordinate).data
fields = ("id", "name", "site_type", "coordinate", "image")


class SiteOverviewSerializer(serializers.ModelSerializer[Site]):
Expand Down
10 changes: 4 additions & 6 deletions canopeum_frontend/src/components/analytics/SiteSummaryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,14 @@ const SiteSummaryCard = ({ site, admins, onSiteChange, onSiteEdit }: Props) => {
</div>

<div className='card-subtitle my-1'>
<div className='d-flex align-items-center text-muted'>
<span className='material-symbols-outlined fill-icon text-muted me-1'>
location_on
</span>
<div className='d-flex align-items-center gap-1 text-muted'>
<span className='material-symbols-outlined fill-icon'>location_on</span>
<span className='text-ellipsis'>
{site.coordinate.address ?? translate('analytics.site-summary.unknown')}
</span>
</div>
<div className='d-flex align-items-center text-muted'>
<span className='material-symbols-outlined fill-icon text-muted me-1'>person</span>
<div className='d-flex align-items-center gap-1 text-muted'>
<span className='material-symbols-outlined fill-icon'>person</span>
<span className='text-ellipsis'>{siteAdminsDisplay}</span>
</div>
</div>
Expand Down
24 changes: 13 additions & 11 deletions canopeum_frontend/src/components/context/LanguageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type ILanguageContext = {
translateValue: (translatable: Translatable) => string,
}

type Translatable = Record<string, string> & {
type Translatable = Record<string, unknown> & {
en: string,
fr: string,
}
Expand Down Expand Up @@ -39,9 +39,17 @@ const LanguageContextProvider: FunctionComponent<{ readonly children?: ReactNode
return new Intl.DateTimeFormat(i18n.language, fullOptions).format(date)
}, [i18n])

const translateValue = useCallback((translable: Translatable) => translable[i18n.language], [
i18n,
])
const translateValue = useCallback(
(translable: Translatable) => {
const value = translable[i18n.language]
if (typeof value !== 'string') {
throw new TypeError(`The value of language '${i18n.language}' is not a string`)
}

return value
},
[i18n],
)

const context = useMemo<ILanguageContext>(() => (
{
Expand All @@ -50,13 +58,7 @@ const LanguageContextProvider: FunctionComponent<{ readonly children?: ReactNode
}
), [formatDate, translateValue])

return (
<LanguageContext.Provider
value={context}
>
{props.children}
</LanguageContext.Provider>
)
return <LanguageContext.Provider value={context}>{props.children}</LanguageContext.Provider>
},
)

Expand Down
23 changes: 13 additions & 10 deletions canopeum_frontend/src/components/social/SiteSocialHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import './SiteSocialHeader.scss'

import { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'

import { AuthenticationContext } from '@components/context/AuthenticationContext'
import { LanguageContext } from '@components/context/LanguageContext'
import IconBadge from '@components/IconBadge'
import ToggleSwitch from '@components/inputs/ToggleSwitch'
import SiteHeaderSponsors from '@components/SiteHeaderSponsors'
import { appRoutes } from '@constants/routes.constant'
import useApiClient from '@hooks/ApiClientHook'
import type { PageViewMode } from '@models/PageViewMode.type'
import { getSiteTypeIconKey } from '@models/SiteType'
Expand All @@ -29,10 +31,10 @@ const SiteSocialHeader = ({ site, viewMode }: Props) => {
const [isFollowing, setIsFollowing] = useState<boolean | undefined>()
const [isPublic, setIsPublic] = useState(!!site.isPublic)

useEffect(() => setIsFollowing(currentUser?.followedSiteIds.includes(site.id)), [
currentUser?.followedSiteIds,
site.id,
])
useEffect(
() => setIsFollowing(currentUser?.followedSiteIds.includes(site.id)),
[currentUser?.followedSiteIds, site.id],
)

useEffect(() => setIsPublic(!!site.isPublic), [site])

Expand Down Expand Up @@ -90,12 +92,13 @@ const SiteSocialHeader = ({ site, viewMode }: Props) => {
<h1 className='card-title mb-0'>{site.name}</h1>

<div className='
d-flex
align-items-center
column-gap-3
row-gap-2
flex-wrap
justify-content-end'>
d-flex
align-items-center
column-gap-3
row-gap-2
flex-wrap
justify-content-end
'>
{viewMode === 'admin' && (
<ToggleSwitch
additionalClassNames='fs-4'
Expand Down
22 changes: 11 additions & 11 deletions canopeum_frontend/src/pages/MapPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ const initialMapLocation = (sites: SiteMap[]) => {
const { minLat, maxLat, minLong, maxLong } = sites.reduce(
(previous, current) => {
// Unset or invalid coordinate should be ignored when trying to pin the center of all sites
if (!current.coordinates.latitude || !current.coordinates.longitude) return previous
if (!current.coordinate.ddLatitude || !current.coordinate.ddLongitude) return previous

return {
minLat: Math.min(previous.minLat, current.coordinates.latitude),
maxLat: Math.max(previous.maxLat, current.coordinates.latitude),
minLong: Math.min(previous.minLong, current.coordinates.longitude),
maxLong: Math.max(previous.maxLong, current.coordinates.longitude),
minLat: Math.min(previous.minLat, current.coordinate.ddLatitude),
maxLat: Math.max(previous.maxLat, current.coordinate.ddLatitude),
minLong: Math.min(previous.minLong, current.coordinate.ddLongitude),
maxLong: Math.max(previous.maxLong, current.coordinate.ddLongitude),
}
},
{ minLat: 90, maxLat: -90, minLong: 180, maxLong: -180 },
Expand Down Expand Up @@ -73,8 +73,8 @@ const MapPage = () => {
site: SiteMap,
mapMarkerEvent?: MarkerEvent<MarkerInstance, MouseEvent>,
) => {
const latitude = mapMarkerEvent?.target._lngLat.lat ?? site.coordinates.latitude
const longitude = mapMarkerEvent?.target._lngLat.lng ?? site.coordinates.longitude
const latitude = mapMarkerEvent?.target._lngLat.lat ?? site.coordinate.ddLatitude
const longitude = mapMarkerEvent?.target._lngLat.lng ?? site.coordinate.ddLongitude
if (!latitude || !longitude) return

setMapViewState({
Expand Down Expand Up @@ -143,8 +143,8 @@ const MapPage = () => {
<NavigationControl position='top-right' showCompass showZoom visualizePitch />
<ScaleControl position='bottom-left' unit='metric' />
{sites.map(site => {
const latitude = Number(site.coordinates.latitude)
const longitude = Number(site.coordinates.longitude)
const latitude = Number(site.coordinate.ddLatitude)
const longitude = Number(site.coordinate.ddLongitude)

return (
<Marker
Expand Down Expand Up @@ -200,9 +200,9 @@ const MapPage = () => {
<span className='ms-1'>{site.siteType.en}</span>
</h6>

<h6 className='d-flex align-items-center text-black-50'>
<h6 className='d-flex align-items-center gap-1 text-muted'>
<span className='material-symbols-outlined fill-icon'>location_on</span>
<span className='ms-1'>{site.coordinates.address}</span>
{site.coordinate.address ?? t('analytics.site-summary.unknown')}
</h6>

<Link
Expand Down
Loading

0 comments on commit b6ce80f

Please sign in to comment.