<commit_before>from collections import defaultdict
import django_filters
from django.db import transaction
from django.db.models import Prefetch
from django.utils.timezone import now
from rest_framework import filters, permissions, response, serializers, status, viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.fields import JSONField
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.response import Response

from democracy.enums import InitialSectionType
from democracy.models import ContactPerson, Hearing, Label, Section, SectionImage
from democracy.pagination import DefaultLimitPagination
from democracy.views.base import AdminsSeeUnpublishedMixin
from democracy.views.contact_person import ContactPersonSerializer
from democracy.views.label import LabelSerializer
from democracy.views.section import (SectionCreateUpdateSerializer, SectionFieldSerializer, SectionImageSerializer,
                                     SectionSerializer)

from .hearing_report import HearingReport


class HearingFilter(django_filters.FilterSet):
    open_at_lte = django_filters.IsoDateTimeFilter(name='open_at', lookup_type='lte')
    open_at_gt = django_filters.IsoDateTimeFilter(name='open_at', lookup_type='gt')

    class Meta:
        model = Hearing
        fields = ['published', 'open_at_lte', 'open_at_gt']


class ContactPersonRelatedSerializer(serializers.Serializer):
    """
    Validator for incoming ContactPerson data.
    """
    id = serializers.CharField(required=True)


class ContactPersonRelatedField(serializers.PrimaryKeyRelatedField):
    """
    Allow selecting ContactPersons by giving their ID in format {"id": <id>}
    """
    def to_representation(self, value):
        return ContactPersonSerializer(instance=value).data

    def to_internal_value(self, data):
        ContactPersonRelatedSerializer(data=data).is_valid(raise_exception=True)
        return super().to_internal_value(data['id'])


class LabelRelatedSerializer(serializers.Serializer):
    """
    Validator for incoming Label data.
    """
    id = serializers.IntegerField(required=True)


class LabelField(serializers.PrimaryKeyRelatedField):
    """
    Allow selecting Labels by giving their ID in format {"id": <id>}
    """
    def to_representation(self, value):
        return LabelSerializer(instance=value).data

    def to_internal_value(self, data):
        LabelRelatedSerializer(data=data).is_valid(raise_exception=True)
        return super().to_internal_value(data['id'])


class HearingCreateUpdateSerializer(serializers.ModelSerializer):
    geojson = JSONField(required=False, allow_null=True)

    # this field is used only for incoming data validation, outgoing data is added manually
    # in to_representation()
    sections = serializers.ListField(child=serializers.DictField(), write_only=True)

    contact_persons = ContactPersonRelatedField(queryset=ContactPerson.objects.all(), many=True)
    labels = LabelField(queryset=Label.objects.all(), many=True)

    class Meta:
        model = Hearing
        fields = [
            'title', 'id', 'borough',
            'published', 'open_at', 'close_at',
            'servicemap_url', 'sections',
            'closed', 'geojson', 'organization', 'slug',
            'contact_persons', 'labels',
        ]

    @transaction.atomic()
    def create(self, validated_data):
        sections_data = validated_data.pop('sections')
        hearing = super().create(validated_data)
<commit_msg>Use NestedPKRelatedField for Labels and ContactPersons in Hearing<commit_after>from collections import defaultdict
import django_filters
from django.db import transaction
from django.db.models import Prefetch
from django.utils.timezone import now
from rest_framework import filters, permissions, response, serializers, status, viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.fields import JSONField
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.response import Response

from democracy.enums import InitialSectionType
from democracy.models import ContactPerson, Hearing, Label, Section, SectionImage
from democracy.pagination import DefaultLimitPagination
from democracy.views.base import AdminsSeeUnpublishedMixin
from democracy.views.contact_person import ContactPersonSerializer
from democracy.views.label import LabelSerializer
from democracy.views.section import (SectionCreateUpdateSerializer, SectionFieldSerializer, SectionImageSerializer,
                                     SectionSerializer)

from .hearing_report import HearingReport
from .utils import NestedPKRelatedField


class HearingFilter(django_filters.FilterSet):
    open_at_lte = django_filters.IsoDateTimeFilter(name='open_at', lookup_type='lte')
    open_at_gt = django_filters.IsoDateTimeFilter(name='open_at', lookup_type='gt')

    class Meta:
        model = Hearing
        fields = ['published', 'open_at_lte', 'open_at_gt']


class HearingCreateUpdateSerializer(serializers.ModelSerializer):
    geojson = JSONField(required=False, allow_null=True)

    # this field is used only for incoming data validation, outgoing data is added manually
    # in to_representation()
    sections = serializers.ListField(child=serializers.DictField(), write_only=True)

    contact_persons = NestedPKRelatedField(queryset=ContactPerson.objects.all(), many=True, expanded=True,
                                           serializer=ContactPersonSerializer)
    labels = NestedPKRelatedField(queryset=Label.objects.all(), many=True, expanded=True, serializer=LabelSerializer)

    class Meta:
        model = Hearing
        fields = [
            'title', 'id', 'borough',
            'published', 'open_at', 'close_at',
            'servicemap_url', 'sections',
            'closed', 'geojson', 'organization', 'slug',
            'contact_persons', 'labels',
        ]

    @transaction.atomic()
    def create(self, validated_data):
        sections_data = validated_data.pop('sections')
        hearing = super().create(validated_data)

{'fraction_non_alphanumeric': 0.08385093167701864, 'fraction_numerical': 0.0, 'mean_word_length': 4.473166368515206, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1431048', 'n_tokens_mistral': 1599, 'n_tokens_neox': 1539, 'n_words': 395}