Skip to content

Commit

Permalink
[#173] Add Branch model management via Django admin and
Browse files Browse the repository at this point in the history
API

- Enable CRUD operations on Branch model through Django admin interface
- Implement BranchViewSet to expose Branch data through API endpoints
- Add BranchSerializer to handle Branch model
serialization/deserialization
- Configure filtering, searching, and ordering for Branch API endpoints
- Enhance admin UI with previews, filtering, and form customizations

This change equips the application with comprehensive tooling to manage
Branch data, allowing administrators to create, update, and maintain
branch information through the admin interface. It also exposes this
data to client applications via a RESTful API, enabling efficient
retrieval, filtering, and manipulation of branch details. Additionally,
the admin UI has been enhanced with previews, filters, and form
customizations for an improved user experience when managing branches.
  • Loading branch information
delano committed Jun 27, 2024
1 parent b9e486e commit eca8204
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 11 deletions.
2 changes: 2 additions & 0 deletions apps/api/afb/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from afbcore.views import (
BranchViewSet,
FoodRequestViewSet,
ProfileViewSet,
authtoken,
Expand All @@ -28,6 +29,7 @@
# e.g. /api/v1/requests/abcdef1234/
router.register("requests", FoodRequestViewSet, basename="foodrequest")
router.register("profiles", ProfileViewSet, basename="profile")
router.register("branches", BranchViewSet)

urlpatterns = [
path("afbadmin/", afbcore_admin.site.urls, name="admin"),
Expand Down
2 changes: 1 addition & 1 deletion apps/api/afbcore/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AuthorAdmin(admin.ModelAdmin):
# Register your models here.
admin.site.register(User, UserAdmin)
admin.site.register(Profile)
admin.site.register(Branch, BranchAdmin)
# admin.site.register(Branch, BranchAdmin)
admin.site.register(FoodRequest)
admin.site.register(Delivery)
admin.site.register(DeliveryRegion)
Expand Down
1 change: 1 addition & 0 deletions apps/api/afbcore/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .branch_serializer import BranchSerializer # noqa: F401
from .delivery_region_serializer import DeliveryRegionSerializer # noqa: F401
from .profile.profile_serializer import ProfileSerializer # noqa: F401
from .request.food_request_serializer import (
Expand Down
67 changes: 67 additions & 0 deletions apps/api/afbcore/serializers/branch_serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from rest_framework import serializers

from ..models import Branch
from .delivery_region_serializer import DeliveryRegionSerializer


class BranchSerializer(serializers.ModelSerializer):
delivery_regions = DeliveryRegionSerializer(many=True, read_only=True)

class Meta:
model = Branch
fields = [
"id",
"display_name",
"delivery_regions",
"pickup_locations",
"frequency_of_requests",
"spay_neuter_requirement",
"pets_per_household_max",
"delivery_deadline_days",
"delivery_type",
"delivery_pickup_details",
"blurb",
"blurb_image",
"latitude",
"longitude",
"delivery_radius",
# Include fields from PhysicalLocationMixin if needed
"street_address",
"city",
"province_state",
"country",
"postal_zip_code",
]
read_only_fields = ["id"]

def validate_delivery_radius(self, value):
if value is not None and value <= 0:
raise serializers.ValidationError(
"Delivery radius must be greater than 0."
)
return value

def validate_pets_per_household_max(self, value):
if value <= 0:
raise serializers.ValidationError(
"Maximum pets per household must be greater than 0."
)
return value

def validate_delivery_deadline_days(self, value):
if value <= 0:
raise serializers.ValidationError(
"Delivery deadline days must be greater than 0."
)
return value

def validate(self, data):
if data.get("latitude") is not None and data.get("longitude") is None:
raise serializers.ValidationError(
"Both latitude and longitude must be provided together."
)
if data.get("latitude") is None and data.get("longitude") is not None:
raise serializers.ValidationError(
"Both latitude and longitude must be provided together."
)
return data
1 change: 1 addition & 0 deletions apps/api/afbcore/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
module.
"""

from .branch_viewset import BranchViewSet # noqa: F401
from .profile import ProfileViewSet # noqa: F401
from .requests import FoodRequestViewSet # noqa: F401
from .users import CurrentUserAPIView # noqa: F401
107 changes: 101 additions & 6 deletions apps/api/afbcore/views/admin/branch_admin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,110 @@
from django.contrib import admin
from django.utils.html import format_html

from .models import Branch
from ...models import Branch


@admin.register(Branch)
class BranchAdmin(admin.ModelAdmin):
list_display = [
list_display = (
"display_name",
"location_name",
"city",
"province_state",
] # Customize as needed
# No need to specify read_only_fields unless you want some fields to be read-only in the admin
"state_or_province",
"country",
"operational",
"hidden",
)
list_filter = (
"operational",
"hidden",
"spay_neuter_requirement",
"delivery_type",
)
search_fields = (
"display_name",
"location_name",
"city",
"state_or_province",
"country",
)
readonly_fields = ("id",)

fieldsets = (
(
"Basic Information",
{
"fields": (
"id",
"display_name",
"blurb",
"blurb_image",
"operational",
"hidden",
)
},
),
(
"Location",
{
"fields": (
"location_name",
"address_line1",
"address_line2",
"city",
"state_or_province",
"postal_code",
"country",
"ext_id",
"latitude",
"longitude",
)
},
),
(
"Delivery Information",
{
"fields": (
"delivery_regions",
"pickup_locations",
"delivery_type",
"delivery_pickup_details",
"delivery_radius",
"delivery_deadline_days",
)
},
),
(
"Branch Policies",
{
"fields": (
"frequency_of_requests",
"spay_neuter_requirement",
"pets_per_household_max",
)
},
),
)

def blurb_image_preview(self, obj):
if obj.blurb_image:
return format_html(
'<img src="{}" width="100" height="100" />', obj.blurb_image.url
)
return "No Image"

blurb_image_preview.short_description = "Blurb Image Preview"

def get_readonly_fields(self, request, obj=None):
if obj: # editing an existing object
return self.readonly_fields + ("id",)
return self.readonly_fields

filter_horizontal = ("delivery_regions",)

admin.site.register(Branch, BranchAdmin)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "delivery_regions":
kwargs["widget"] = admin.widgets.FilteredSelectMultiple(
"Delivery Regions", is_stacked=False
)
return super().formfield_for_manytomany(db_field, request, **kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from rest_framework import filters, viewsets
from rest_framework.permissions import IsAuthenticated

from .models import Branch
from .serializers import BranchSerializer
from ..models import Branch
from ..serializers import BranchSerializer


class BranchViewSet(viewsets.ModelViewSet):
Expand All @@ -21,8 +21,20 @@ class BranchViewSet(viewsets.ModelViewSet):
"delivery_type",
"spay_neuter_requirement",
]
search_fields = ["display_name", "city", "province_state", "country"]
ordering_fields = ["display_name", "city", "province_state", "country"]
search_fields = [
"display_name",
"location_name",
"city",
"state_or_province",
"country",
]
ordering_fields = [
"display_name",
"location_name",
"city",
"state_or_province",
"country",
]

def get_queryset(self):
"""
Expand Down

0 comments on commit eca8204

Please sign in to comment.