我有一个问题,我的序列化器中缺少一些数据(在验证和创建函数中)。 我的模特:
class PostsImage(models.Model):
image = models.ImageField(upload_to="posts/images")
post = models.ForeignKey(
"socialmedia.Post", on_delete=models.CASCADE, related_name="posts_images"
)
class Meta:
verbose_name = "PostsImage"
verbose_name_plural = "PostsImages"
def __str__(self):
return self.image
class PostsAttachedFile(models.Model):
attached = models.FileField(upload_to="posts/attached")
post = models.ForeignKey(
"socialmedia.Post",
on_delete=models.CASCADE,
related_name="posts_attached_files",
)
class Meta:
verbose_name = "PostsAttachedFile"
verbose_name_plural = "PostsAttachedFiles"
def __str__(self):
return self.attached
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
business_sector = models.CharField(max_length=50, choices=BUSINESS_SECTOR_CHOICES)
author = models.ForeignKey(
"users.User", on_delete=models.CASCADE, related_name="posts"
)
created_at = models.DateTimeField(auto_now_add=True)
valid_until = models.DateTimeField()
location_type = models.CharField(max_length=20, choices=LOCATION_CHOICES)
location = models.CharField(max_length=255)
location_radius = models.PositiveIntegerField(null=True, blank=True)
class Meta:
verbose_name = "Post"
verbose_name_plural = "Posts"
def __str__(self):
return self.title
我的序列化器:
class PostSerializer(serializers.ModelSerializer):
images = PostsImageSerializer(many=True, source="posts_images", required=False)
attached_files = PostsAttachedFileSerializer(
many=True, source="posts_attached_files", required=False
)
class Meta:
model = Post
fields = "__all__"
def validate(self, attrs):
print(attrs)
author = attrs.get("author")
location_type = attrs.get("location_type")
location_radius = attrs.get("location_radius")
valid_until = attrs.get("valid_until")
user = self.context["request"].user
if author and author != user:
raise serializers.ValidationError(
{"author": "You can't create post for another user"}
)
if location_type == "local" and not location_radius:
raise serializers.ValidationError(
{
"location_radius": "You must provide location radius if location type is local",
}
)
if valid_until and valid_until.timestamp() < datetime.now().timestamp():
raise serializers.ValidationError(
{"valid_until": "Valid until must be in the future"}
)
print("#### validation ok")
return attrs
def create(self, validated_data):
print(validated_data)
images = None
attached_files = None
if "posts_images" in validated_data.keys():
images = validated_data.pop("posts_images")
if "posts_attached_files" in validated_data.keys():
attached_files = validated_data.pop("posts_attached_files")
post = Post.objects.create(**validated_data)
images_from_files = self.context["request"].FILES.getlist("posts_images")
print(images_from_files)
print(self.context["request"].data)
if images_from_files:
for image in images_from_files:
PostsImage.objects.create(post=post, image=image)
if attached_files:
for attached_file in attached_files:
PostsAttachedFile.objects.create(post=post, attached_file=attached_file)
return post
def update(self, instance, validated_data):
images = None
attached_files = None
if "posts_images" in validated_data.keys():
images = validated_data.pop("posts_images")
if "posts_attached_files" in validated_data.keys():
attached_files = validated_data.pop("posts_attached_files")
instance = super().update(instance, validated_data)
if images and type(images) == list:
instance.posts_images.all().delete()
for image in images:
PostsImage.objects.create(post=instance, **image)
if attached_files and type(attached_files) == list:
instance.posts_attached_files.all().delete()
for attached_file in attached_files:
PostsAttachedFile.objects.create(post=instance, **attached_file)
return instance
我的观点:
class PostModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
最后是我的测试代码:
class PostModelViewSetTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(
email="[email protected]", password="testpassword"
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_create_post(self):
tmp_image = SimpleUploadedFile(
"file.jpg", b"file_content", content_type="image/jpg"
)
img_path = f"{CURRENT_DIR}/socialmedia/tests/default.png"
image = SimpleUploadedFile(
name="test_image.jpg",
content=open(img_path, "rb").read(),
content_type="image/jpeg",
)
data = {
"author": self.user.id,
"location_type": "local",
"location": "nord",
"location_radius": 5,
"valid_until": datetime.now() + timedelta(days=1),
"content": "content",
"title": "title",
"business_sector": "IT",
"images": [
{"image": image},
{"image": image},
{"image": image},
{"image": image},
],
}
response = self.client.post("/socialmedia/posts/", data, format="multipart")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Post.objects.count(), 1)
print(PostsImage.objects.all())
self.assertEqual(PostsImage.objects.count(), 4)
post = Post.objects.first()
self.assertEqual(post.author, self.user)
self.assertEqual(post.posts_images.count(), 4)
在我的测试中,我想创建一个包含多个图像的帖子。如果我删除图像,它工作正常,但如果我保留它们,则会创建 post 对象,但不会创建 PostsImage。因此测试失败,因为没有创建 4 个 PostsImage 对象。
不会创建 PostsImage 对象,因为如果我在序列化程序中进行打印(
print(attrs)
在验证中)
我明白了:
.OrderedDict([('title', 'title'), ('content', 'content'), ('business_sector', 'IT'), ('valid_until', datetime.datetime(2023, 9, 18, 9, 34, 22, 220328, tzinfo=zoneinfo. ZoneInfo(key='Europe/Paris'))), ('location_type', 'local'), ('location', 'nord'), ('location_radius', 5), ('author', <User: >))])
缺少“图片”或“posts_images”。
如果在我的测试中我将
response = self.client.post("/socialmedia/posts/", data, format="multipart")
更改为 response = self.client.post("/socialmedia/posts/", data, format="json")
,我可以在序列化器中看到 posts_images,但它不起作用,因为它们是文件。
所以我尝试使用
images_from_files = self.context["request"].FILES.getlist("posts_images")
但是self.context[“request”].FILES是空的
您必须在设置方法中创建任意数量的假图像,每次运行测试时,您的图像都会自动创建。
from django.core.files.uploadedfile import SimpleUploadedFile
image = BytesIO()
Image.new('RGB', (100, 100)).save(image, 'JPEG')
image.seek(0)
self.image_1 = SimpleUploadedFile('image.JPG', image.getvalue())
这就是创建单个图像的方法。您可以循环执行此操作并创建多个图像。 确保所有数据都应在 setUp 方法中设置,这是编写测试用例的最佳实践。