class DynamicModelViewSet(WithDynamicViewSetMixin, viewsets.ModelViewSet):

    ENABLE_BULK_PARTIAL_CREATION = settings.ENABLE_BULK_PARTIAL_CREATION
    ENABLE_BULK_UPDATE = settings.ENABLE_BULK_UPDATE
    ENABLE_PATCH_ALL = settings.ENABLE_PATCH_ALL

    def _get_bulk_payload(self, request):
        plural_name = self.get_serializer_class().get_plural_name()
        if isinstance(request.data, list):
            return request.data
        elif plural_name in request.data and len(request.data) == 1:
            return request.data[plural_name]
        return None

    def _bulk_update(self, data, partial=False):
        # Restrict the update to the filtered queryset.
        serializer = self.get_serializer(
            self.filter_queryset(self.get_queryset()),
            data=data,
            many=True,
            partial=partial
        )
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def _validate_patch_all(self, data):
        if not isinstance(data, dict):
            raise ValidationError(
                'Patch-all data must be in object form'
            )
        serializer = self.get_serializer()
        fields = serializer.get_all_fields()
        validated = {}
        for name, value in six.iteritems(data):
            field = fields.get(name, None)
            if field is None:
                raise ValidationError(
                    'Unknown field: "%s"' % name
                )
            source = field.source or name
            if source == '*' or field.read_only:
                raise ValidationError(
                    'Cannot update field: "%s"' % name
                )
            validated[source] = value
        return validated

    def _patch_all_query(self, queryset, data):
        # update by queryset
        try:
            return queryset.update(**data)
        except Exception as e:
            raise ValidationError(
                'Failed to bulk-update records:\n'
                '%s\n'
                'Data: %s' % (
                    str(e),
                    str(data)
                )
            )

    def _patch_all_loop(self, queryset, data):
        # update by transaction loop
        updated = 0
        try:
            with transaction.atomic():
                for record in queryset:
                    for k, v in six.iteritems(data):
                        setattr(record, k, v)
                    record.save()
                    updated += 1
                return updated
        except IntegrityError as e:
            raise ValidationError(
                'Failed to update records:\n'
                '%s\n'
                'Data: %s' % (
                    str(e),
                    str(data)
                )
            )

    def _patch_all(self, data, query=False):
        queryset = self.filter_queryset(self.get_queryset())
        data = self._validate_patch_all(data)
        updated = (
            self._patch_all_query(queryset, data) if query
            else self._patch_all_loop(queryset, data)
        )
        return Response({
            'meta': {
                'updated': updated
            }
        }, status=status.HTTP_200_OK)

    def update(self, request, *args, **kwargs):
        """Update one or more model instances.

        If ENABLE_BULK_UPDATE is set, multiple previously-fetched records
        may be updated in a single call, provided their IDs.

        If ENABLE_PATCH_ALL is set, multiple records
        may be updated in a single PATCH call, even without knowing their IDs.

        *WARNING*: ENABLE_PATCH_ALL should be considered an advanced feature
        and used with caution. This feature must be enabled at the viewset level
        and must also be requested explicitly by the client
        via the "patch-all" query parameter.

        This parameter can have one of the following values:

            true (or 1): records will be fetched and then updated in a transaction loop
              - The `Model.save` method will be called and model signals will run
              - This can be slow if there are too many signals or many records in the query
              - This is considered the more safe and default behavior
            query: records will be updated in a single query
              - The `QuerySet.update` method will be called and model signals will not run
              - This will be fast, but may break data constraints that are controlled by signals
              - This is considered unsafe but useful in certain situations

        The server's successful response to a patch-all request
        will NOT include any individual records. Instead, the response content will contain
        a "meta" object with an "updated" count of updated records.

        Examples:

        Update one dog:

            PATCH /dogs/1/
            {
                'fur': 'white'
            }

        Update many dogs by ID:

            PATCH /dogs/
            [
                {'id': 1, 'fur': 'white'},
                {'id': 2, 'fur': 'black'},
                {'id': 3, 'fur': 'yellow'}
            ]

        Update all dogs in a query:

            PATCH /dogs/?filter{fur.contains}=brown&patch-all=true
            {
                'fur': 'gold'
            }
        """  # noqa
        if self.ENABLE_BULK_UPDATE:
            patch_all = self.get_request_patch_all()
            if self.ENABLE_PATCH_ALL and patch_all:
                # patch-all update
                data = request.data
                return self._patch_all(
                    data,
                    query=(patch_all == 'query')
                )
            else:
                # bulk payload update
                partial = 'partial' in kwargs
                bulk_payload = self._get_bulk_payload(request)
                if bulk_payload:
                    return self._bulk_update(bulk_payload, partial)

        # singular update
        try:
            return super(DynamicModelViewSet, self).update(request, *args,
                                                           **kwargs)
        except AssertionError as e:
            err = str(e)
            if 'Fix your URL conf' in err:
                # this error is returned by DRF if a client
                # makes an update request (PUT or PATCH) without an ID
                # since DREST supports bulk updates with IDs contained in data,
                # we return a 400 instead of a 500 for this case,
                # as this is not considered a misconfiguration
                raise exceptions.ValidationError(err)
            else:
                raise

    def _create_many(self, data):
        items = []
        errors = []
        result = {}
        serializers = []

        for entry in data:
            serializer = self.get_serializer(data=entry)
            try:
                serializer.is_valid(raise_exception=True)
            except exceptions.ValidationError as e:
                errors.append({
                    'detail': str(e),
                    'source': entry
                })
            else:
                if self.ENABLE_BULK_PARTIAL_CREATION:
                    self.perform_create(serializer)
                    items.append(
                        serializer.to_representation(serializer.instance))
                else:
                    serializers.append(serializer)
        if not self.ENABLE_BULK_PARTIAL_CREATION and not errors:
            for serializer in serializers:
                self.perform_create(serializer)
                items.append(
                    serializer.to_representation(serializer.instance))

        # Populate serialized data to the result.
        result = SideloadingProcessor(
            self.get_serializer(),
            items
        ).data

        # Include errors if any.
        if errors:
            result['errors'] = errors

        code = (status.HTTP_201_CREATED if not errors else
                status.HTTP_400_BAD_REQUEST)

        return Response(result, status=code)

    def create(self, request, *args, **kwargs):
        """
        Either create a single or many model instances in bulk
        using the Serializer's many=True ability from Django REST >= 2.2.5.

        The data can be represented by the serializer name (single or plural
        forms), dict or list.

        Examples:

        POST /dogs/
        {
          "name": "Fido",
          "age": 2
        }

        POST /dogs/
        {
          "dog": {
            "name": "Lucky",
            "age": 3
          }
        }

        POST /dogs/
        {
          "dogs": [
            {"name": "Fido", "age": 2},
            {"name": "Lucky", "age": 3}
          ]
        }

        POST /dogs/
        [
            {"name": "Fido", "age": 2},
            {"name": "Lucky", "age": 3}
        ]
        """
        bulk_payload = self._get_bulk_payload(request)
        if bulk_payload:
            return self._create_many(bulk_payload)
        return super(DynamicModelViewSet, self).create(
            request, *args, **kwargs)

    def _destroy_many(self, data):
        instances = self.get_queryset().filter(
            id__in=[d['id'] for d in data]
        ).distinct()
        for instance in instances:
            self.check_object_permissions(self.request, instance)
            self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def destroy(self, request, *args, **kwargs):
        """
        Either delete a single or many model instances in bulk

        DELETE /dogs/
        {
            "dogs": [
                {"id": 1},
                {"id": 2}
            ]
        }

        DELETE /dogs/
        [
            {"id": 1},
            {"id": 2}
        ]
        """
        bulk_payload = self._get_bulk_payload(request)
        if bulk_payload:
            return self._destroy_many(bulk_payload)
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        if lookup_url_kwarg not in kwargs:
            # assume that it is a poorly formatted bulk request
            return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
        return super(DynamicModelViewSet, self).destroy(
            request, *args, **kwargs
        ) class Car(models.Model):
    name = models.CharField(max_length=60)
    country = models.ForeignKey(Country, on_delete=models.CASCADE) class Cat(models.Model):
    name = models.TextField()
    home = models.ForeignKey('Location', on_delete=models.CASCADE)
    backup_home = models.ForeignKey(
        'Location',
        related_name='friendly_cats',
        on_delete=models.CASCADE
    )
    hunting_grounds = models.ManyToManyField(
        'Location',
        related_name='annoying_cats',
        related_query_name='getoffmylawn'
    )
    parent = models.ForeignKey(
        'Cat',
        null=True,
        blank=True,
        related_name='kittens',
        on_delete=models.CASCADE
    ) class Dog(models.Model):
    name = models.TextField()
    fur_color = models.TextField()
    origin = models.TextField() class Group(models.Model):
    name = models.TextField(unique=True)
    permissions = models.ManyToManyField('Permission', related_name='groups') class Horse(models.Model):
    name = models.TextField()
    origin = models.TextField() class Location(models.Model):
    name = models.TextField()
    blob = models.TextField() class Permission(models.Model):
    name = models.TextField()
    code = models.IntegerField() class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    display_name = models.TextField()
    thumbnail_url = models.TextField(null=True, blank=True) class User(models.Model):
    name = models.TextField()
    last_name = models.TextField()
    groups = models.ManyToManyField('Group', related_name='users')
    permissions = models.ManyToManyField('Permission', related_name='users')
    date_of_birth = models.DateField(null=True, blank=True)
    # 'related_name' intentionally left unset in location field below:
    location = models.ForeignKey(
        'Location',
        null=True,
        blank=True,
        on_delete=models.CASCADE
    )
    favorite_pet_type = models.ForeignKey(
        ContentType,
        null=True,
        blank=True,
        on_delete=models.CASCADE
    )
    favorite_pet_id = models.TextField(null=True, blank=True)
    favorite_pet = GenericForeignKey(
        'favorite_pet_type',
        'favorite_pet_id',
    )
    is_dead = models.BooleanField(null=True, default=False) class Zebra(models.Model):
    name = models.TextField()
    origin = models.TextField() class CarSerializer(DynamicModelSerializer):
    country = DynamicRelationField('CountrySerializer')
    parts = DynamicRelationField('PartSerializer', many=True, source='part_set')  # noqa

    class Meta:
        model = Car
        fields = ('id', 'name', 'country', 'parts')
        deferred_fields = ('name', 'country', 'parts') class CatSerializer(DynamicModelSerializer):
    home = DynamicRelationField('LocationSerializer', link=None)
    backup_home = DynamicRelationField(
        'LocationSerializer', link=backup_home_link)
    foobar = DynamicRelationField(
        'LocationSerializer', source='hunting_grounds', many=True)
    parent = DynamicRelationField('CatSerializer', immutable=True)

    class Meta:
        model = Cat
        name = 'cat'
        fields = ('id', 'name', 'home', 'backup_home', 'foobar', 'parent')
        deferred_fields = ('home', 'backup_home', 'foobar', 'parent')
        immutable_fields = ('name',)
        untrimmed_fields = ('name',) class DogSerializer(DynamicModelSerializer):

    class Meta:
        model = Dog
        fields = ('id', 'name', 'origin', 'fur', 'is_red')

    fur = CharField(source='fur_color')
    is_red = DynamicMethodField(deferred=True, requires=['fur_color'])

    def get_is_red(self, instance):
        return instance.fur_color == 'red' class GroupSerializer(DynamicModelSerializer):

    class Meta:
        model = Group
        name = 'group'
        fields = (
            'id',
            'name',
            'permissions',
            'members',
            'users',
            'loc1users',
            'loc1usersLambda'
        )

    permissions = DynamicRelationField(
        'PermissionSerializer',
        many=True,
        deferred=True)
    members = DynamicRelationField(
        'UserSerializer',
        source='users',
        many=True,
        deferred=True)

    # Intentional duplicate of 'users':
    users = DynamicRelationField(
        'UserSerializer',
        many=True,
        deferred=True)

    # For testing default queryset on relations:
    loc1users = DynamicRelationField(
        'UserSerializer',
        source='users',
        many=True,
        queryset=User.objects.filter(location_id=1),
        deferred=True)

    loc1usersLambda = DynamicRelationField(
        'UserSerializer',
        source='users',
        many=True,
        queryset=lambda srlzr: User.objects.filter(location_id=1),
        deferred=True) class HorseSerializer(DynamicModelSerializer):

    class Meta:
        model = Horse
        name = 'horse'
        fields = (
            'id',
            'name',
            'origin',
        ) class LocationSerializer(DynamicModelSerializer):

    class Meta:
        defer_many_relations = False
        model = Location
        name = 'location'
        fields = (
            'id', 'name', 'users', 'user_count', 'address',
            'cats', 'friendly_cats', 'bad_cats'
        )

    users = DynamicRelationField(
        'UserSerializer',
        source='user_set',
        many=True,
        deferred=True
    )
    user_count = CountField('users', required=False, deferred=True)
    address = DynamicField(source='blob', required=False, deferred=True)
    cats = DynamicRelationField(
        'CatSerializer', source='cat_set', many=True, deferred=True)
    friendly_cats = DynamicRelationField(
        'CatSerializer', many=True, deferred=True)
    bad_cats = DynamicRelationField(
        'CatSerializer', source='annoying_cats', many=True, deferred=True)

    def filter_queryset(self, query):
        return query.exclude(name='Atlantis') class PermissionSerializer(DynamicModelSerializer):

    class Meta:
        defer_many_relations = True
        model = Permission
        name = 'permission'
        fields = ('id', 'name', 'code', 'users', 'groups')
        deferred_fields = ('code',)

    users = DynamicRelationField('UserSerializer', many=True, deferred=False)
    groups = DynamicRelationField('GroupSerializer', many=True) class ProfileSerializer(DynamicModelSerializer):

    class Meta:
        model = Profile
        name = 'profile'
        fields = (
            'user',
            'display_name',
            'thumbnail_url',
            'user_location_name',
        )

    user = DynamicRelationField('UserSerializer')
    user_location_name = DynamicField(
        source='user.location.name',
        requires=['user.location.name'],
        read_only=True
    ) class UserLocationSerializer(UserSerializer):
    """ Serializer to test embedded fields """
    class Meta:
        model = User
        name = 'user_location'
        fields = ('groups', 'location', 'id')

    location = DynamicRelationField('LocationSerializer', embed=True)
    groups = DynamicRelationField('GroupSerializer', many=True, embed=True) class UserSerializer(DynamicModelSerializer):

    class Meta:
        model = User
        name = 'user'
        fields = (
            'id',
            'name',
            'permissions',
            'groups',
            'location',
            'last_name',
            'display_name',
            'thumbnail_url',
            'number_of_cats',
            'profile',
            'date_of_birth',
            'favorite_pet_id',
            'favorite_pet',
            'is_dead',
        )
        deferred_fields = (
            'last_name',
            'date_of_birth',
            'display_name',
            'profile',
            'thumbnail_url',
            'favorite_pet_id',
            'favorite_pet',
            'is_dead',
        )
        read_only_fields = ('profile',)

    location = DynamicRelationField('LocationSerializer')
    permissions = DynamicRelationField(
        'PermissionSerializer',
        many=True,
        deferred=True
    )
    groups = DynamicRelationField('GroupSerializer', many=True, deferred=True)
    display_name = DynamicField(source='profile.display_name', read_only=True)
    thumbnail_url = DynamicField(
        source='profile.thumbnail_url',
        read_only=True
    )
    number_of_cats = DynamicMethodField(
        requires=['location.cat_set.*'],
        deferred=True
    )

    # Don't set read_only on this field directly. Used in test for
    # Meta.read_only_fields.
    profile = DynamicRelationField(
        'ProfileSerializer',
        deferred=True
    )
    favorite_pet = DynamicGenericRelationField(required=False)

    def get_number_of_cats(self, user):
        if not self.context.get('request'):
            # Used in test_api.py::test_relation_includes_context
            raise Exception("No request object in context")
        location = user.location
        return len(location.cat_set.all()) if location else 0 class ZebraSerializer(DynamicModelSerializer):

    class Meta:
        model = Zebra
        name = 'zebra'
        fields = (
            'id',
            'name',
            'origin',
        )
from rest_framework import exceptions
from django.db.models import Q
from dynamic_rest.viewsets import DynamicModelViewSet
from tests.models import (
    Car,
    Cat,
    Dog,
    Group,
    Horse,
    Location,
    Permission,
    Profile,
    User,
    Zebra
)
from tests.serializers import (
    CarSerializer,
    CatSerializer,
    DogSerializer,
    GroupSerializer,
    HorseSerializer,
    LocationSerializer,
    PermissionSerializer,
    ProfileSerializer,
    UserLocationSerializer,
    UserSerializer,
    ZebraSerializer
)
                from django.utils.datastructures import MergeDict



class UserViewSet(DynamicModelViewSet):
    features = (
        DynamicModelViewSet.INCLUDE, DynamicModelViewSet.EXCLUDE,
        DynamicModelViewSet.FILTER, DynamicModelViewSet.SORT,
        DynamicModelViewSet.SIDELOADING, DynamicModelViewSet.DEBUG
    )
    model = User
    serializer_class = UserSerializer
    queryset = User.objects.all()

    def get_queryset(self):
        location = self.request.query_params.get('location')
        qs = self.queryset
        if location:
            qs = qs.filter(location=location)
        return qs

    def list(self, request, *args, **kwargs):
        query_params = self.request.query_params
        # for testing query param injection
        if query_params.get('name'):
            query_params.add('filter{name}', query_params.get('name'))
        return super(UserViewSet, self).list(request, *args, **kwargs)


class GroupNoMergeDictViewSet(DynamicModelViewSet):
    model = Group
    serializer_class = GroupSerializer
    queryset = Group.objects.all()

    def create(self, request, *args, **kwargs):
        response = super(GroupNoMergeDictViewSet, self).create(
            request,
            *args,
            **kwargs
        )
        if hasattr(request, 'data'):
            try:
                # Django<1.9, DRF<3.2
                if isinstance(request.data, MergeDict):
                    raise exceptions.ValidationError(
                        "request.data is MergeDict"
                    )
                elif not isinstance(request.data, dict):
                    raise exceptions.ValidationError(
                        "request.data is not a dict"
                    )
            except BaseException:
                pass

        return response


class GroupViewSet(DynamicModelViewSet):
    features = (
        DynamicModelViewSet.INCLUDE, DynamicModelViewSet.EXCLUDE,
        DynamicModelViewSet.FILTER, DynamicModelViewSet.SORT
    )

    model = Group