Skip to content

Commit f9bf70b

Browse files
committed
Fix post images
1 parent 67eb86f commit f9bf70b

File tree

5 files changed

+66
-10
lines changed

5 files changed

+66
-10
lines changed

api/serializers.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,10 @@ class SimplePhotoSerializer(serializers.ModelSerializer):
533533
source="photo", use_url=True, read_only=True,
534534
help_text="URL of the photo associated with the item. Note: This URL may change over time. Do not rely on it for permanent storage."
535535
)
536-
file = serializers.ImageField(required=True, source="photo", write_only=True)
537536

538537
class Meta:
539538
model = Photo
540-
fields = ("uuid", "url", "file")
539+
fields = ("uuid", "url")
541540
read_only_fields = (
542541
"uuid",
543542
)
@@ -736,19 +735,46 @@ class Meta:
736735
read_only_fields = fields
737736

738737
class BaseReportWithPhotosSerializer(BaseReportSerializer):
739-
photos = SimplePhotoSerializer(required=True, many=True)
738+
739+
def get_fields(self):
740+
fields = super().get_fields()
741+
request = self.context.get("request")
742+
743+
# Use different field behavior depending on request method
744+
if request and request.method in ("POST", "PUT", "PATCH"):
745+
# Write mode — accept uploaded image files
746+
fields["photos"] = serializers.ListField(
747+
child=serializers.ImageField(required=True),
748+
write_only=True,
749+
min_length=1,
750+
)
751+
else:
752+
# Read mode — return nested photo serializer
753+
fields["photos"] = SimplePhotoSerializer(many=True, read_only=True)
754+
755+
return fields
740756

741757
@transaction.atomic
742758
def create(self, validated_data):
743759
photos = validated_data.pop("photos", [])
744760

745761
instance = super().create(validated_data)
746762

763+
# NOTE: do not use bulk here.
747764
for photo in photos:
748-
_ = Photo.objects.create(report=instance, **photo)
765+
_ = Photo.objects.create(report=instance, photo=photo)
749766

750767
return instance
751768

769+
def to_representation(self, instance):
770+
"""
771+
Always serialize output using the read-only `photos` definition,
772+
even if this serializer was initialized in write mode.
773+
"""
774+
# Rebind `photos` temporarily for output
775+
self.fields["photos"] = SimplePhotoSerializer(many=True, read_only=True)
776+
return super().to_representation(instance)
777+
752778
class Meta(BaseReportSerializer.Meta):
753779
fields = BaseReportSerializer.Meta.fields + ("photos",)
754780

api/tests/integration/breeding_sites/create.tavern.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ stages:
4242
Authorization: "Bearer {app_user_token:s}"
4343
method: "POST"
4444
files:
45-
photos[0]file: "{test_jpg_image_path}"
46-
photos[1]file: "{test_png_image_path}"
45+
photos[0]: "{test_jpg_image_path}"
46+
photos[1]: "{test_png_image_path}"
4747
data: &request_site_data
4848
created_at: '2024-01-01T00:00:00Z'
4949
sent_at: '2024-01-01T00:30:00Z'
@@ -112,6 +112,6 @@ stages:
112112
request:
113113
<<: *request_breeding_site
114114
files:
115-
photos[0]file: "{test_non_image_path}"
115+
photos[0]: "{test_non_image_path}"
116116
response:
117117
status_code: 400

api/tests/integration/observations/create.tavern.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ stages:
4242
Authorization: "Bearer {app_user_token:s}"
4343
method: "POST"
4444
files:
45-
photos[0]file: "{test_jpg_image_path}"
46-
photos[1]file: "{test_png_image_path}"
45+
photos[0]: "{test_jpg_image_path}"
46+
photos[1]: "{test_png_image_path}"
4747
data: &request_site_data
4848
created_at: '2024-01-01T00:00:00Z'
4949
sent_at: '2024-01-01T00:30:00Z'
@@ -114,6 +114,6 @@ stages:
114114
request:
115115
<<: *request_observation
116116
files:
117-
photos[0]file: "{test_non_image_path}"
117+
photos[0]: "{test_non_image_path}"
118118
response:
119119
status_code: 400
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 3.2.25 on 2025-10-16 13:53
2+
3+
from django.db import migrations, models
4+
import tigaserver_app.models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('tigaserver_app', '0085_auto_20250822_1214'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='historicalreport',
16+
name='report_id',
17+
field=models.CharField(db_index=True, default=tigaserver_app.models.generate_report_id, help_text='4-digit alpha-numeric code generated on user phone to identify each unique report from that user. Digits should lbe randomly drawn from the set of all lowercase and uppercase alphabetic characters and 0-9, but excluding 0, o, and O to avoid confusion if we ever need user to be able to refer to a report ID in correspondence with MoveLab (as was previously the case when we had them sending samples).', max_length=4),
18+
),
19+
migrations.AlterField(
20+
model_name='report',
21+
name='report_id',
22+
field=models.CharField(db_index=True, default=tigaserver_app.models.generate_report_id, help_text='4-digit alpha-numeric code generated on user phone to identify each unique report from that user. Digits should lbe randomly drawn from the set of all lowercase and uppercase alphabetic characters and 0-9, but excluding 0, o, and O to avoid confusion if we ever need user to be able to refer to a report ID in correspondence with MoveLab (as was previously the case when we had them sending samples).', max_length=4),
23+
),
24+
]

tigaserver_app/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
from PIL import Image
1212
import pydenticon
1313
import os
14+
import random
1415
from slugify import slugify
16+
import string
1517
from typing import List, Optional, Union
1618
import uuid
1719

@@ -781,6 +783,9 @@ class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
781783
class Meta(GenericUUIDTaggedItemBase.Meta, TaggedItemBase.Meta):
782784
abstract = False
783785

786+
def generate_report_id():
787+
return ''.join(random.choices(string.ascii_letters + string.digits, k=4))
788+
784789
class Report(TimeZoneModelMixin, models.Model):
785790
TYPE_BITE = "bite"
786791
TYPE_ADULT = "adult"
@@ -849,6 +854,7 @@ class Report(TimeZoneModelMixin, models.Model):
849854
report_id = models.CharField(
850855
max_length=4,
851856
db_index=True,
857+
default=generate_report_id,
852858
help_text="4-digit alpha-numeric code generated on user phone to identify each unique report from that user. Digits should lbe randomly drawn from the set of all lowercase and uppercase alphabetic characters and 0-9, but excluding 0, o, and O to avoid confusion if we ever need user to be able to refer to a report ID in correspondence with MoveLab (as was previously the case when we had them sending samples).",
853859
)
854860

0 commit comments

Comments
 (0)