From b3253b42836acd123224e88c0927f1ee6a031d94 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 29 Aug 2014 12:35:53 +0100 Subject: [PATCH] Remove `.model` usage in tests. Remove the shortcut `.model` view attribute usage from test cases. --- rest_framework/generics.py | 49 +++++---------------- tests/serializers.py | 7 --- tests/test_filters.py | 65 +++++++++++++++++++--------- tests/test_generics.py | 34 +++++++++++---- tests/test_hyperlinkedserializers.py | 62 ++++++++++++++++++-------- tests/test_nullable_fields.py | 13 +++++- tests/test_pagination.py | 32 ++++++++++---- tests/test_permissions.py | 24 +++++++--- tests/test_response.py | 5 ++- tests/test_validation.py | 4 +- tests/views.py | 8 ---- 11 files changed, 185 insertions(+), 118 deletions(-) delete mode 100644 tests/serializers.py delete mode 100644 tests/views.py diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 09035303f..68222864f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -51,11 +51,6 @@ class GenericAPIView(views.APIView): queryset = None serializer_class = None - # This shortcut may be used instead of setting either or both - # of the `queryset`/`serializer_class` attributes, although using - # the explicit style is generally preferred. - model = None - # If you want to use object lookups other than pk, set this attribute. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk' @@ -71,9 +66,8 @@ class GenericAPIView(views.APIView): # The filter backend classes to use for queryset filtering filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - # The following attributes may be subject to change, + # The following attribute may be subject to change, # and should be considered private API. - model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS paginator_class = Paginator def get_serializer_context(self): @@ -199,26 +193,13 @@ class GenericAPIView(views.APIView): (Eg. admins get full serialization, others get basic serialization) """ - serializer_class = self.serializer_class - if serializer_class is not None: - return serializer_class - - warnings.warn( - 'The `.model` attribute on view classes is now deprecated in favor ' - 'of the more explicit `serializer_class` and `queryset` attributes.', - DeprecationWarning, stacklevel=2 + assert self.serializer_class is not None, ( + "'%s' should either include a `serializer_class` attribute, " + "or override the `get_serializer_class()` method." + % self.__class__.__name__ ) - assert self.model is not None, \ - "'%s' should either include a 'serializer_class' attribute, " \ - "or use the 'model' attribute as a shortcut for " \ - "automatically generating a serializer class." \ - % self.__class__.__name__ - - class DefaultSerializer(self.model_serializer_class): - class Meta: - model = self.model - return DefaultSerializer + return self.serializer_class def get_queryset(self): """ @@ -235,19 +216,13 @@ class GenericAPIView(views.APIView): (Eg. return a list of items that is specific to the user) """ - if self.queryset is not None: - return self.queryset._clone() + assert self.queryset is not None, ( + "'%s' should either include a `queryset` attribute, " + "or override the `get_queryset()` method." + % self.__class__.__name__ + ) - if self.model is not None: - warnings.warn( - 'The `.model` attribute on view classes is now deprecated in favor ' - 'of the more explicit `serializer_class` and `queryset` attributes.', - DeprecationWarning, stacklevel=2 - ) - return self.model._default_manager.all() - - error_format = "'%s' must define 'queryset' or 'model'" - raise ImproperlyConfigured(error_format % self.__class__.__name__) + return self.queryset._clone() def get_object(self): """ diff --git a/tests/serializers.py b/tests/serializers.py deleted file mode 100644 index be7b37722..000000000 --- a/tests/serializers.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import serializers -from tests.models import NullableForeignKeySource - - -class NullableFKSourceSerializer(serializers.ModelSerializer): - class Meta: - model = NullableForeignKeySource diff --git a/tests/test_filters.py b/tests/test_filters.py index 47bffd436..6f24b1abb 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -16,9 +16,14 @@ factory = APIRequestFactory() if django_filters: + class FilterableItemSerializer(serializers.ModelSerializer): + class Meta: + model = FilterableItem + # Basic filter on a list view. class FilterFieldsRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer filter_fields = ['decimal', 'date'] filter_backends = (filters.DjangoFilterBackend,) @@ -33,7 +38,8 @@ if django_filters: fields = ['text', 'decimal', 'date'] class FilterClassRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (filters.DjangoFilterBackend,) @@ -46,12 +52,14 @@ if django_filters: fields = ['text'] class IncorrectlyConfiguredRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer filter_class = MisconfiguredFilter filter_backends = (filters.DjangoFilterBackend,) class FilterClassDetailView(generics.RetrieveAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (filters.DjangoFilterBackend,) @@ -63,15 +71,12 @@ if django_filters: model = BaseFilterableItem class BaseFilterableItemFilterRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer filter_class = BaseFilterableItemFilter filter_backends = (filters.DjangoFilterBackend,) # Regression test for #814 - class FilterableItemSerializer(serializers.ModelSerializer): - class Meta: - model = FilterableItem - class FilterFieldsQuerysetView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer @@ -323,6 +328,11 @@ class SearchFilterModel(models.Model): text = models.CharField(max_length=100) +class SearchFilterSerializer(serializers.ModelSerializer): + class Meta: + model = SearchFilterModel + + class SearchFilterTests(TestCase): def setUp(self): # Sequence of title/text is: @@ -342,7 +352,8 @@ class SearchFilterTests(TestCase): def test_search(self): class SearchListView(generics.ListAPIView): - model = SearchFilterModel + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer filter_backends = (filters.SearchFilter,) search_fields = ('title', 'text') @@ -359,7 +370,8 @@ class SearchFilterTests(TestCase): def test_exact_search(self): class SearchListView(generics.ListAPIView): - model = SearchFilterModel + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer filter_backends = (filters.SearchFilter,) search_fields = ('=title', 'text') @@ -375,7 +387,8 @@ class SearchFilterTests(TestCase): def test_startswith_search(self): class SearchListView(generics.ListAPIView): - model = SearchFilterModel + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer filter_backends = (filters.SearchFilter,) search_fields = ('title', '^text') @@ -392,7 +405,8 @@ class SearchFilterTests(TestCase): def test_search_with_nonstandard_search_param(self): with temporary_setting('SEARCH_PARAM', 'query', module=filters): class SearchListView(generics.ListAPIView): - model = SearchFilterModel + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer filter_backends = (filters.SearchFilter,) search_fields = ('title', 'text') @@ -418,6 +432,11 @@ class OrderingFilterRelatedModel(models.Model): related_name="relateds") +class OrderingFilterSerializer(serializers.ModelSerializer): + class Meta: + model = OrdringFilterModel + + class OrderingFilterTests(TestCase): def setUp(self): # Sequence of title/text is: @@ -440,7 +459,8 @@ class OrderingFilterTests(TestCase): def test_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -459,7 +479,8 @@ class OrderingFilterTests(TestCase): def test_reverse_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -478,7 +499,8 @@ class OrderingFilterTests(TestCase): def test_incorrectfield_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -497,7 +519,8 @@ class OrderingFilterTests(TestCase): def test_default_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = ('title',) oredering_fields = ('text',) @@ -516,7 +539,8 @@ class OrderingFilterTests(TestCase): def test_default_ordering_using_string(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = 'title' ordering_fields = ('text',) @@ -545,7 +569,7 @@ class OrderingFilterTests(TestCase): new_related.save() class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = 'title' ordering_fields = '__all__' @@ -567,7 +591,8 @@ class OrderingFilterTests(TestCase): def test_ordering_with_nonstandard_ordering_param(self): with temporary_setting('ORDERING_PARAM', 'order', filters): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + queryset = OrdringFilterModel.objects.all() + serializer_class = OrderingFilterSerializer filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) diff --git a/tests/test_generics.py b/tests/test_generics.py index e9f5bebdd..f50d53e99 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -11,18 +11,30 @@ from tests.models import ForeignKeySource, ForeignKeyTarget factory = APIRequestFactory() +class BasicSerializer(serializers.ModelSerializer): + class Meta: + model = BasicModel + + +class ForeignKeySerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource + + class RootView(generics.ListCreateAPIView): """ Example description for OPTIONS. """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer class InstanceView(generics.RetrieveUpdateDestroyAPIView): """ Example description for OPTIONS. """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer def get_queryset(self): queryset = super(InstanceView, self).get_queryset() @@ -33,7 +45,8 @@ class FKInstanceView(generics.RetrieveUpdateDestroyAPIView): """ FK: example description for OPTIONS. """ - model = ForeignKeySource + queryset = ForeignKeySource.objects.all() + serializer_class = ForeignKeySerializer class SlugSerializer(serializers.ModelSerializer): @@ -48,7 +61,7 @@ class SlugBasedInstanceView(InstanceView): """ A model with a slug-field. """ - model = SlugBasedModel + queryset = SlugBasedModel.objects.all() serializer_class = SlugSerializer lookup_field = 'slug' @@ -503,7 +516,7 @@ class TestOverriddenGetObject(TestCase): """ Example detail view for override of get_object(). """ - model = BasicModel + serializer_class = BasicSerializer def get_object(self): pk = int(self.kwargs['pk']) @@ -573,7 +586,7 @@ class ClassASerializer(serializers.ModelSerializer): class ExampleView(generics.ListCreateAPIView): serializer_class = ClassASerializer - model = ClassA + queryset = ClassA.objects.all() class TestM2MBrowseableAPI(TestCase): @@ -603,7 +616,7 @@ class TwoFieldModel(models.Model): class DynamicSerializerView(generics.ListCreateAPIView): - model = TwoFieldModel + queryset = TwoFieldModel.objects.all() renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer) def get_serializer_class(self): @@ -612,8 +625,11 @@ class DynamicSerializerView(generics.ListCreateAPIView): class Meta: model = TwoFieldModel fields = ('field_b',) - return DynamicSerializer - return super(DynamicSerializerView, self).get_serializer_class() + else: + class DynamicSerializer(serializers.ModelSerializer): + class Meta: + model = TwoFieldModel + return DynamicSerializer class TestFilterBackendAppliedToViews(TestCase): diff --git a/tests/test_hyperlinkedserializers.py b/tests/test_hyperlinkedserializers.py index d45485391..0e8c1ed46 100644 --- a/tests/test_hyperlinkedserializers.py +++ b/tests/test_hyperlinkedserializers.py @@ -39,59 +39,85 @@ class AlbumSerializer(serializers.ModelSerializer): fields = ('title', 'url') +class BasicSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = BasicModel + + +class AnchorSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Anchor + + +class ManyToManySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ManyToManyModel + + +class BlogPostSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPost + + +class OptionalRelationSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = OptionalRelationModel + + class BasicList(generics.ListCreateAPIView): - model = BasicModel - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer class BasicDetail(generics.RetrieveUpdateDestroyAPIView): - model = BasicModel - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer class AnchorDetail(generics.RetrieveAPIView): - model = Anchor - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = Anchor.objects.all() + serializer_class = AnchorSerializer class ManyToManyList(generics.ListAPIView): - model = ManyToManyModel - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = ManyToManyModel.objects.all() + serializer_class = ManyToManySerializer class ManyToManyDetail(generics.RetrieveAPIView): - model = ManyToManyModel - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = ManyToManyModel.objects.all() + serializer_class = ManyToManySerializer class BlogPostCommentListCreate(generics.ListCreateAPIView): - model = BlogPostComment + queryset = BlogPostComment.objects.all() serializer_class = BlogPostCommentSerializer class BlogPostCommentDetail(generics.RetrieveAPIView): - model = BlogPostComment + queryset = BlogPostComment.objects.all() serializer_class = BlogPostCommentSerializer class BlogPostDetail(generics.RetrieveAPIView): - model = BlogPost + queryset = BlogPost.objects.all() + serializer_class = BlogPostSerializer class PhotoListCreate(generics.ListCreateAPIView): - model = Photo - model_serializer_class = PhotoSerializer + queryset = Photo.objects.all() + serializer_class = PhotoSerializer class AlbumDetail(generics.RetrieveAPIView): - model = Album + queryset = Album.objects.all() serializer_class = AlbumSerializer lookup_field = 'title' class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): - model = OptionalRelationModel - model_serializer_class = serializers.HyperlinkedModelSerializer + queryset = OptionalRelationModel.objects.all() + serializer_class = OptionalRelationSerializer urlpatterns = patterns( diff --git a/tests/test_nullable_fields.py b/tests/test_nullable_fields.py index 0c133fc2c..8d0c84bb0 100644 --- a/tests/test_nullable_fields.py +++ b/tests/test_nullable_fields.py @@ -1,10 +1,19 @@ from django.core.urlresolvers import reverse from django.conf.urls import patterns, url +from rest_framework import serializers, generics from rest_framework.test import APITestCase from tests.models import NullableForeignKeySource -from tests.serializers import NullableFKSourceSerializer -from tests.views import NullableFKSourceDetail + + +class NullableFKSourceSerializer(serializers.ModelSerializer): + class Meta: + model = NullableForeignKeySource + + +class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = NullableForeignKeySource.objects.all() + serializer_class = NullableFKSourceSerializer urlpatterns = patterns( diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 80c33e2eb..8f9e0005e 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -4,7 +4,7 @@ from decimal import Decimal from django.core.paginator import Paginator from django.test import TestCase from django.utils import unittest -from rest_framework import generics, status, pagination, filters, serializers +from rest_framework import generics, serializers, status, pagination, filters from rest_framework.compat import django_filters from rest_framework.test import APIRequestFactory from .models import BasicModel, FilterableItem @@ -22,11 +22,22 @@ def split_arguments_from_url(url): return path, args +class BasicSerializer(serializers.ModelSerializer): + class Meta: + model = BasicModel + + +class FilterableItemSerializer(serializers.ModelSerializer): + class Meta: + model = FilterableItem + + class RootView(generics.ListCreateAPIView): """ Example description for OPTIONS. """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer paginate_by = 10 @@ -34,14 +45,16 @@ class DefaultPageSizeKwargView(generics.ListAPIView): """ View for testing default paginate_by_param usage """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer class PaginateByParamView(generics.ListAPIView): """ View for testing custom paginate_by_param usage """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer paginate_by_param = 'page_size' @@ -49,7 +62,8 @@ class MaxPaginateByView(generics.ListAPIView): """ View for testing custom max_paginate_by usage """ - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer paginate_by = 3 max_paginate_by = 5 paginate_by_param = 'page_size' @@ -140,7 +154,8 @@ class IntegrationTestPaginationAndFiltering(TestCase): fields = ['text', 'decimal', 'date'] class FilterFieldsRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer paginate_by = 10 filter_class = DecimalFilter filter_backends = (filters.DjangoFilterBackend,) @@ -188,7 +203,8 @@ class IntegrationTestPaginationAndFiltering(TestCase): return queryset.filter(decimal__lt=Decimal(request.GET['decimal'])) class BasicFilterFieldsRootView(generics.ListCreateAPIView): - model = FilterableItem + queryset = FilterableItem.objects.all() + serializer_class = FilterableItemSerializer paginate_by = 10 filter_backends = (DecimalFilterBackend,) @@ -387,7 +403,7 @@ class TestContextPassedToCustomField(TestCase): def test_with_pagination(self): class ListView(generics.ListCreateAPIView): - model = BasicModel + queryset = BasicModel.objects.all() serializer_class = BasicModelSerializer paginate_by = 1 diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 93f8020f3..b90ba4f19 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import User, Permission, Group from django.db import models from django.test import TestCase from django.utils import unittest -from rest_framework import generics, status, permissions, authentication, HTTP_HEADER_ENCODING +from rest_framework import generics, serializers, status, permissions, authentication, HTTP_HEADER_ENCODING from rest_framework.compat import guardian, get_model_name from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.test import APIRequestFactory @@ -13,14 +13,21 @@ import base64 factory = APIRequestFactory() +class BasicSerializer(serializers.ModelSerializer): + class Meta: + model = BasicModel + + class RootView(generics.ListCreateAPIView): - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer authentication_classes = [authentication.BasicAuthentication] permission_classes = [permissions.DjangoModelPermissions] class InstanceView(generics.RetrieveUpdateDestroyAPIView): - model = BasicModel + queryset = BasicModel.objects.all() + serializer_class = BasicSerializer authentication_classes = [authentication.BasicAuthentication] permission_classes = [permissions.DjangoModelPermissions] @@ -167,6 +174,11 @@ class BasicPermModel(models.Model): ) +class BasicPermSerializer(serializers.ModelSerializer): + class Meta: + model = BasicPermModel + + # Custom object-level permission, that includes 'view' permissions class ViewObjectPermissions(permissions.DjangoObjectPermissions): perms_map = { @@ -181,7 +193,8 @@ class ViewObjectPermissions(permissions.DjangoObjectPermissions): class ObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView): - model = BasicPermModel + queryset = BasicPermModel.objects.all() + serializer_class = BasicPermSerializer authentication_classes = [authentication.BasicAuthentication] permission_classes = [ViewObjectPermissions] @@ -189,7 +202,8 @@ object_permissions_view = ObjectPermissionInstanceView.as_view() class ObjectPermissionListView(generics.ListAPIView): - model = BasicPermModel + queryset = BasicPermModel.objects.all() + serializer_class = BasicPermSerializer authentication_classes = [authentication.BasicAuthentication] permission_classes = [ViewObjectPermissions] diff --git a/tests/test_response.py b/tests/test_response.py index 2eff83d3d..004c565c9 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -86,14 +86,15 @@ class HTMLView1(APIView): class HTMLNewModelViewSet(viewsets.ModelViewSet): - model = BasicModel + serializer_class = BasicModelSerializer + queryset = BasicModel.objects.all() class HTMLNewModelView(generics.ListCreateAPIView): renderer_classes = (BrowsableAPIRenderer,) permission_classes = [] serializer_class = BasicModelSerializer - model = BasicModel + queryset = BasicModel.objects.all() new_model_viewset_router = routers.DefaultRouter() diff --git a/tests/test_validation.py b/tests/test_validation.py index e13e4078c..f62d9068b 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -22,7 +22,7 @@ class ValidationModelSerializer(serializers.ModelSerializer): class UpdateValidationModel(generics.RetrieveUpdateDestroyAPIView): - model = ValidationModel + queryset = ValidationModel.objects.all() serializer_class = ValidationModelSerializer @@ -117,7 +117,7 @@ class ValidationMaxValueValidatorModelSerializer(serializers.ModelSerializer): class UpdateMaxValueValidationModel(generics.RetrieveUpdateDestroyAPIView): - model = ValidationMaxValueValidatorModel + queryset = ValidationMaxValueValidatorModel.objects.all() serializer_class = ValidationMaxValueValidatorModelSerializer diff --git a/tests/views.py b/tests/views.py deleted file mode 100644 index 55935e924..000000000 --- a/tests/views.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import generics -from .models import NullableForeignKeySource -from .serializers import NullableFKSourceSerializer - - -class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView): - model = NullableForeignKeySource - model_serializer_class = NullableFKSourceSerializer