模型序列化器中未调用字段验证器

问题描述 投票:0回答:1

我对 Django 还很陌生。我正在开发一个需要存储影片的 django 应用程序,当我尝试使用以下程序加载影片实例时:

import os
import json
import requests

# Assuming the JSON content is stored in a variable named 'films_json'
json_file_path: str = os.path.join(os.getcwd(), "films.json")

# Load the JSON content from the file
with open(json_file_path, "r") as file:
    films_json: dict = json.load(file)

# URL of the endpoint
endpoint_url = "http://127.0.0.1:8000/site-admin/add-film/"

for film_data in films_json["films"]:
    print()
    print(json.dumps(film_data, indent=4))
    response: requests.Response = requests.post(endpoint_url, json=film_data)
    if response.status_code == 201:
        print(f"Film '{film_data['name']}' added successfully")
    else:
        print(f"Failed to add film '{film_data['name']}':")
        print(json.dumps(dict(response.headers),indent=4))
        print(f"{json.dumps(response.json(), indent=4)}")

我收到以下回复(针对每部电影):

{
    "name": "Pulp Fiction",
    "genre": "Crime",
    "duration": 154,
    "director": "Quentin Tarantino",
    "cast": [
        "John Travolta",
        "Uma Thurman",
        "Samuel L. Jackson",
        "Bruce Willis"
    ],
    "description": "The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.",
    "image_url": "https://m.media-amazon.com/images/M/MV5BNGNhMDIzZTUtNTBlZi00MTRlLWFjM2ItYzViMjE3YzI5MjljXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_.jpg",
    "release": "1994-00-00"
}
Failed to add film 'Pulp Fiction':
Bad Request
{
    "Date": "Fri, 03 May 2024 22:35:32 GMT",
    "Server": "WSGIServer/0.2 CPython/3.10.10",
    "Content-Type": "application/json",
    "Vary": "Accept, Cookie",
    "Allow": "POST, OPTIONS",
    "djdt-store-id": "bf9dc157db354419a70d69f3be5e8dd7",
    "Server-Timing": "TimerPanel_utime;dur=0;desc=\"User CPU time\", TimerPanel_stime;dur=0;desc=\"System CPU time\", TimerPanel_total;dur=0;desc=\"Total CPU time\", TimerPanel_total_time;dur=50.99039999186061;desc=\"Elapsed time\", SQLPanel_sql_time;dur=2.606799971545115;desc=\"SQL 14 queries\", CachePanel_total_time;dur=0;desc=\"Cache 0 Calls\"",
    "X-Frame-Options": "DENY",
    "Content-Length": "197",
    "X-Content-Type-Options": "nosniff",
    "Referrer-Policy": "same-origin",
    "Cross-Origin-Opener-Policy": "same-origin"
}
{
    "release": [
        "Date has wrong format. Use one of these formats instead: YYYY-MM-DD."
    ],
    "director_id": [
        "Invalid pk \"1\" - object does not exist."
    ],
    "cast": [
        "Invalid pk \"2\" - object does not exist."
    ]
}

这是我的观点:

class AggregateFilmView(generics.CreateAPIView):
    serializer_class: type = FilmSerializer

    def post(self, request) -> Response:

        print(f"###     {type(request.data)}", file=sys.stderr)
        with transaction.atomic():  # Ensure all or nothing persistence

            try:
                film_data: dict = request.data

                # Handle Director
                if "director" not in film_data.keys():
                    raise ValidationError("director field must be provided")
                director_name: str = film_data.pop("director", {})
                if not type(director_name) is str:
                    raise ValidationError("diretor name must be a string")
                director_data: dict[str, str] = {"name": director_name}
                director_serializer = DirectorSerializer(data=director_data)
                director_serializer.is_valid(raise_exception=True)
                director: Director = director_serializer.save()

                # Handle Cast (Actors)
                if "cast" not in film_data.keys():
                    raise ValidationError("cast field must be provided")
                cast_names: list[str] = film_data.pop("cast", [])
                if type(cast_names) is not list:
                    raise ValidationError("cast must be a list of names (list[string])")
                cast_data: list[dict[str, str]] = []
                if not len(cast_names) == 0:
                    for name in cast_names:
                        if not type(name) is str:
                            raise ValidationError(
                                "cast must be a list of names (list[string])"
                            )
                        actor_data: dict[str, str] = {"name": name}
                        cast_data.append(actor_data)
                    actor_serializer = ActorSerializer(data=cast_data, many=True)
                    actor_serializer.is_valid(raise_exception=True)
                    actors: Actor = actor_serializer.save()

                # Handle Film
                film_data["director_id"] = director.id  # director ID
                film_data["cast"] = [actor.id for actor in actors]  # list of actor IDs
                types: list[type] = [type(field) for field in film_data.values()]
                print(types, file=sys.stderr)
                print(film_data, file=sys.stderr)
                film_serializer = FilmSerializer(data=film_data)
                film_serializer.is_valid(raise_exception=True)
                film_serializer.save()

                response: Response = Response(
                    {"detail": "Film added succesfully"}, status=status.HTTP_201_CREATED
                )

            except ValidationError as error:
                response = Response(
                    {"error": error.message},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Return serialized data (customize as needed)
            return response

这是我的序列化器:

class FilmSerializer(serializers.ModelSerializer):

    class Meta:
        model = Film
        # fields: str = "__all__"
        fields: list[str] = [
            "name",
            "release",
            "genre",
            "description",
            "duration",
            "director_id",
            "cast",
        ]

    def validate_name(self, value) -> str:
        print(f"Name validation {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("Film name is required")
        return value

    def validate_release(self, value) -> str:
        print(f"Date validation {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("Release year is required")
        try:
            patterns: dict[str, str] = {
                "%Y": r"^\d{4}$",
                "%Y-%m": r"^\d{4}-\d{2}$",
                "%Y-%m-%d": r"^\d{4}-\d{2}-\d{2}$",
                "%m-%Y": r"^\d{2}-\d{4}$",
                "%d-%m-%Y": r"^\d{2}-\d{2}-\d{4}$",
            }
            format_match: bool = False
            for date_format, pattern in patterns.items():
                if re.match(pattern, value):
                    format_match = True
                    print(f"Matched {date_format} format", file=sys.stderr)
                    return datetime.datetime.strptime(value, date_format)

            if not format_match:
                raise serializers.ValidationError(
                    "Invalid date format.",
                    "Accepted formats: %Y, %Y-%m, %Y-%m-%d, %m-%Y, %d-%m-%Y",
                )

        except ValueError:
            raise serializers.ValidationError("Year must be a valid date")
        return value

    def validate_genre(self, value) -> str:
        print(f"Genre validation {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("Genre is required")
        if value not in Film.GENRE_CHOICES:
            raise serializers.ValidationError("Invalid genre")
        return value

    def validate_description(self, value) -> str:
        print(f"Description validation {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("Description is required")
        if len(value) > 300:
            raise serializers.ValidationError(
                "Description cannot exceed 300 characters"
            )
        return value

    def validate_director_id(self, value) -> str:
        print(f"Director Id {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("Director is required")
        try:
            Director.objects.get(pk=value)
        except Director.DoesNotExist:
            raise serializers.ValidationError("Invalid director ID")
        return value

    def validate_cast(self, value) -> str:
        print(f"Cast {value}", file=sys.stderr)
        if value is None:
            raise serializers.ValidationError("At least one cast member is required")
        try:
            Actor.objects.filter(pk__in=value)
        except Actor.DoesNotExist:
            raise serializers.ValidationError("Invalid cast ID")

这是我的模型:

class Film(models.Model):

    GENRE_CHOICES: list[str] = [
        "Action",
        "Comedy",
        "Crime",
        "Doctumentary",
        "Drama",
        "Horror",
        "Romance",
        "Sci-Fi",
        "Thriller",
        "Western",
    ]

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=64, unique=True)
    release = models.DateField()
    genre = models.CharField(max_length=64)
    description = models.TextField()
    duration = models.FloatField()
    director_id = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="director"
    )
    cast = models.ManyToManyField(User, related_name="cast")

问题在于,release 字段被与我在序列化器中定义的验证不同的验证所拒绝,并且应该通过修改我的 validate_release 方法应该执行的值来避免它报告的无效。

问题是 validate_release 方法没有被调用,正如在运行服务器的终端中可以看到的:

###     <class 'dict'>
[<class 'str'>, <class 'str'>, <class 'int'>, <class 'str'>, <class 'str'>, <class 'str'>, <class 'int'>, <class 'list'>]
{'name': 'Pulp Fiction', 'genre': 'Crime', 'duration': 154, 'description': "The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.", 'image_url': 'https://m.media-amazon.com/images/M/MV5BNGNhMDIzZTUtNTBlZi00MTRlLWFjM2ItYzViMjE3YzI5MjljXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_.jpg', 'release': '1994-00-00', 'director_id': 1, 'cast': [2, 3, 4, 5]}
Name validation Pulp Fiction
Genre validation Crime
Description validation The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.
Bad Request: /site-admin/add-film/
[04/May/2024 00:36:34] "POST /site-admin/add-film/ HTTP/1.1" 400 197

关于如何解决这个问题有什么想法吗?

python python-3.x django serialization django-rest-framework
1个回答
0
投票

因为

ModelSerializer 
会将
release
字段映射到
DateField
。这样,根据框架定义的默认输入日期格式进行验证。

为了覆盖它,您需要设置自己的有效输入列表(我将在 settings.py 中执行此操作,但当然可以在任何地方)。

DATE_INPUT_FORMATS = [
    "%Y",
    "%Y-%m",
    "%m-%Y",
    "%Y-%m-%d",
    "%d-%m-%Y",
]

序列化器.py

class FilmSerializer(serializers.ModelSerializer):
    release = serializers.DateField(input_formats=settings.DATE_INPUT_FORMATS)

    class Meta:
        model = Film
        fields = "__all__"
© www.soinside.com 2019 - 2024. All rights reserved.