def get_fields(self): fields = super(HyperlinkedModelSerializer, self).get_fields() if api_settings.URL_FIELD_NAME in fields: # replace HyperlinkedIdentityField with one that's namespaceable fields[api_settings.URL_FIELD_NAME] = HyperlinkedIdentityField( help_text="object detail API URL", **get_url_kwargs(getattr(self.Meta, 'model'))) return fields
class ArticleSerializer(serializers.HyperlinkedModelSerializer): url = serializers.HyperlinkedIdentityField(**get_url_kwargs(Article)) original_url = serializers.CharField(source='url') class Meta: model = Article fields = ( 'url', 'original_url', 'source', 'title', 'title_keywords', 'categories', 'retrieved_date', 'modified_date', 'original_post_date', )
def get_fields(self): declared_fields = copy.deepcopy(self._declared_fields) ret = OrderedDict() model = getattr(self.Meta, 'model') fields = getattr(self.Meta, 'fields', None) exclude = getattr(self.Meta, 'exclude', None) depth = getattr(self.Meta, 'depth', 0) extra_kwargs = getattr(self.Meta, 'extra_kwargs', {}) assert not (fields and exclude), "Cannot set both 'fields' and 'exclude'." extra_kwargs = self._include_additional_options(extra_kwargs) # Retrieve metadata about fields & relationships on the model class. info = model_meta.get_field_info(model) # Use the default set of field names if none is supplied explicitly. if fields is None: fields = self._get_default_field_names(declared_fields, info) exclude = getattr(self.Meta, 'exclude', None) if exclude is not None: for field_name in exclude: fields.remove(field_name) # Determine the set of model fields, and the fields that they map to. # We actually only need this to deal with the slightly awkward case # of supporting `unique_for_date`/`unique_for_month`/`unique_for_year`. model_field_mapping = {} for field_name in fields: if field_name in declared_fields: field = declared_fields[field_name] source = field.source or field_name else: try: source = extra_kwargs[field_name]['source'] except KeyError: source = field_name # Model fields will always have a simple source mapping, # they can't be nested attribute lookups. if '.' not in source and source != '*': model_field_mapping[source] = field_name # Determine if we need any additional `HiddenField` or extra keyword # arguments to deal with `unique_for` dates that are required to # be in the input data in order to validate it. hidden_fields = {} unique_constraint_names = set() for model_field_name, field_name in model_field_mapping.items(): try: model_field = model._meta.get_field(model_field_name) except FieldDoesNotExist: continue # Include each of the `unique_for_*` field names. unique_constraint_names |= set([ model_field.unique_for_date, model_field.unique_for_month, model_field.unique_for_year ]) unique_constraint_names -= set([None]) # Include each of the `unique_together` field names, # so long as all the field names are included on the serializer. for parent_class in [model] + list(model._meta.parents.keys()): for unique_together_list in parent_class._meta.unique_together: if set(fields).issuperset(set(unique_together_list)): unique_constraint_names |= set(unique_together_list) # Now we have all the field names that have uniqueness constraints # applied, we can add the extra 'required=...' or 'default=...' # arguments that are appropriate to these fields, or add a `HiddenField` for it. for unique_constraint_name in unique_constraint_names: # Get the model field that is refered too. unique_constraint_field = model._meta.get_field(unique_constraint_name) if getattr(unique_constraint_field, 'auto_now_add', None): default = CreateOnlyDefault(timezone.now) elif getattr(unique_constraint_field, 'auto_now', None): default = timezone.now elif unique_constraint_field.has_default(): default = unique_constraint_field.default else: default = empty if unique_constraint_name in model_field_mapping: # The corresponding field is present in the serializer if unique_constraint_name not in extra_kwargs: extra_kwargs[unique_constraint_name] = {} if default is empty: if 'required' not in extra_kwargs[unique_constraint_name]: extra_kwargs[unique_constraint_name]['required'] = True else: if 'default' not in extra_kwargs[unique_constraint_name]: extra_kwargs[unique_constraint_name]['default'] = default elif default is not empty: # The corresponding field is not present in the, # serializer. We have a default to use for it, so # add in a hidden field that populates it. hidden_fields[unique_constraint_name] = HiddenField(default=default) # Now determine the fields that should be included on the serializer. for field_name in fields: if field_name in declared_fields: # Field is explicitly declared on the class, use that. ret[field_name] = declared_fields[field_name] continue elif field_name in info.fields_and_pk: # Create regular model fields. model_field = info.fields_and_pk[field_name] field_cls = self._field_mapping[model_field] kwargs = get_field_kwargs(field_name, model_field) if 'choices' in kwargs: # Fields with choices get coerced into `ChoiceField` # instead of using their regular typed field. field_cls = ChoiceField if not issubclass(field_cls, ModelField): # `model_field` is only valid for the fallback case of # `ModelField`, which is used when no other typed field # matched to the model field. kwargs.pop('model_field', None) if not issubclass(field_cls, CharField): # `allow_blank` is only valid for textual fields. kwargs.pop('allow_blank', None) elif field_name in info.relations: # Create forward and reverse relationships. relation_info = info.relations[field_name] if depth: field_cls = self._get_nested_class(depth, relation_info) kwargs = get_nested_relation_kwargs(relation_info) else: field_cls = self._related_class kwargs = get_relation_kwargs(field_name, relation_info) # `view_name` is only valid for hyperlinked relationships. if not issubclass(field_cls, HyperlinkedRelatedField): kwargs.pop('view_name', None) elif hasattr(model, field_name): # Create a read only field for model methods and properties. field_cls = ReadOnlyField kwargs = {} elif field_name == api_settings.URL_FIELD_NAME: # Create the URL field. field_cls = HyperlinkedIdentityField kwargs = get_url_kwargs(model) else: raise ImproperlyConfigured( 'Field name `%s` is not valid for model `%s`.' % (field_name, model.__class__.__name__) ) # Check that any fields declared on the class are # also explicity included in `Meta.fields`. missing_fields = set(declared_fields.keys()) - set(fields) if missing_fields: missing_field = list(missing_fields)[0] raise ImproperlyConfigured( 'Field `%s` has been declared on serializer `%s`, but ' 'is missing from `Meta.fields`.' % (missing_field, self.__class__.__name__) ) # Populate any kwargs defined in `Meta.extra_kwargs` extras = extra_kwargs.get(field_name, {}) if extras.get('read_only', False): for attr in [ 'required', 'default', 'allow_blank', 'allow_null', 'min_length', 'max_length', 'min_value', 'max_value', 'validators', 'queryset' ]: kwargs.pop(attr, None) if extras.get('default') and kwargs.get('required') is False: kwargs.pop('required') kwargs.update(extras) # Create the serializer field. ret[field_name] = field_cls(**kwargs) for field_name, field in hidden_fields.items(): ret[field_name] = field return ret