def update(self, instance, validated_data): """ Override default update method to counteract race conditions. (See https://github.com/encode/django-rest-framework/issues/5897) :param instance: :param validated_data: :return: """ raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) update_fields = [] for attr, value in validated_data.items(): update_fields.append(attr) if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) # if instance.variable_info and instance.epsilon \ # and instance.user_step == instance.DepositorSteps.STEP_0600_EPSILON_SET: # instance.is_complete = True # else: # instance.is_complete = False # This needs to be added to tell the save method to update the value. update_fields.append('is_complete') # Specifically for this model, we are overriding the update method with an explicit list of # update_fields, so we need to set the updated field manually. # All other models will be updated without this step due to the auto_now option from the parent class. instance.updated = timezone.now() instance.save(update_fields=update_fields) return instance
def update(self, instance, validated_data): raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) if 'name' in validated_data: model = Album name = validated_data['name'] validated_data['slug'] = slug_generator(name, model) from_zone = pytz.timezone(TIME_ZONE) if not 'created_at' in validated_data: validated_data['created_at'] = instance.created_at else: validated_data['created_at'] = validated_data[ 'created_at'].astimezone(from_zone) if not 'updated_at' in validated_data: validated_data['updated_at'] = timezone.now() else: validated_data['updated_at'] = validated_data[ 'updated_at'].astimezone(from_zone) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def equal_to_current(cls, json, fields_to_ignore=("id", "change_date", "changed_by")): """ Compares for equality this instance to a model instance constructed from the supplied JSON. This will ignore any fields in `fields_to_ignore`. Note that this method cannot handle fields with many-to-many associations, as those can only be set on a saved model instance (and saving the model instance will create a new entry). All many-to-many field entries will be removed before the equality comparison is done. Args: json: json representing an entry to compare fields_to_ignore: List of fields that should not be compared for equality. By default includes `id`, `change_date`, and `changed_by`. Returns: True if the checked fields are all equivalent, else False """ # Remove many-to-many relationships from json. # They require an instance to be already saved. info = model_meta.get_field_info(cls) for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in json): json.pop(field_name) new_instance = cls(**json) key_field_args = tuple(getattr(new_instance, key) for key in cls.KEY_FIELDS) current = cls.current(*key_field_args) # If current.id is None, no entry actually existed and the "current" method created it. if current.id is not None: return current.fields_equal(new_instance, fields_to_ignore) return False
def create(self, validated_attrs): # Check that the user isn't trying to handle a writable nested field. # If we don't do this explicitly they'd likely get a confusing # error at the point of calling `Model.objects.create()`. assert not any( isinstance(field, BaseSerializer) and not field.read_only for field in self.fields.values() ), ( 'The `.create()` method does not suport nested writable fields ' 'by default. Write an explicit `.create()` method for serializer ' '`%s.%s`, or set `read_only=True` on nested serializer fields.' % (self.__class__.__module__, self.__class__.__name__) ) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_attrs. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_attrs): many_to_many[field_name] = validated_attrs.pop(field_name) instance = ModelClass.objects.create(**validated_attrs) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def _filter_fields_by_permissions(self, fields, permissions_kind): """ Filter serializer fields by permissions kind :param fields: serializer fields list :param permissions_kind: edit/view :return: fields allowed to interact with """ model = self.Meta.model targets_map = { Permission.get_target(model, field): field for field in fields } pk_fields = [] pk_target = Permission.get_target(model, 'pk') if pk_target in targets_map: pk_fields.append(targets_map.pop(pk_target)) pk_field = model_meta.get_field_info(model).pk pk_target = Permission.get_target(model, pk_field) if pk_target in targets_map: pk_fields.append(targets_map.pop(pk_target)) allowed_targets = Permission.apply_permissions(self.permissions, targets_map.keys(), permissions_kind) allowed_fields = list( map(lambda target: targets_map[target], allowed_targets)) if allowed_fields: allowed_fields.extend(pk_fields) return allowed_fields
def update(self, instance, validated_data): # 修改商品数量 nums = validated_data["nums"] if instance.goods.online and nums != 1: raise serializers.ValidationError({'nums': '线上课程数目只能为1'}) else: from rest_framework.utils import model_meta info = model_meta.get_field_info(instance) # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don't need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) instance.save() # Note that many-to-many fields are set after updating instance. # Setting m2m fields triggers signals which could potentially change # updated instance and we do not want it to collide with .update() for attr, value in m2m_fields: field = getattr(instance, attr) field.set(value) return instance
def create(self, validated_attrs): # Check that the user isn't trying to handle a writable nested field. # If we don't do this explicitly they'd likely get a confusing # error at the point of calling `Model.objects.create()`. assert not any( isinstance(field, BaseSerializer) and not field.read_only for field in self.fields.values() ), ('The `.create()` method does not suport nested writable fields ' 'by default. Write an explicit `.create()` method for serializer ' '`%s.%s`, or set `read_only=True` on nested serializer fields.' % (self.__class__.__module__, self.__class__.__name__)) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_attrs. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_attrs): many_to_many[field_name] = validated_attrs.pop(field_name) instance = ModelClass.objects.create(**validated_attrs) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def _build_field(self): model = getattr(self.parent.Meta, 'model') depth = getattr(self.parent.Meta, 'depth', 0) info = model_meta.get_field_info(model) # Determine any extra field arguments and hidden fields that # should be included extra_kwargs = self.parent.get_extra_kwargs() extra_kwargs.update(self._kwargs) extra_kwargs, hidden_fields = self.parent.get_uniqueness_extra_kwargs( [self.field_name], [self], extra_kwargs ) extra_field_kwargs = { key: value for key, value in self._kwargs.items() if key not in ['read_field'] } # Determine the serializer field class and keyword arguments. field_class, field_kwargs = self.parent.build_field( self.field_name, info, model, depth ) # Include any kwargs defined in `Meta.extra_kwargs` extra_field_kwargs.update( extra_kwargs.get(self.field_name, {}) ) field_kwargs = self.parent.include_extra_kwargs( field_kwargs, extra_field_kwargs ) # Create the serializer field. return field_class(**field_kwargs)
def create(self, validated_data): """Override of create method.""" """ We have a bit of extra checking around this in order to provide descriptive messages when something goes wrong, but this method is essentially just: return ExampleModel.objects.create(**validated_data) If there are many to many fields present on the instance then they cannot be set until the model is instantiated, in which case the implementation is like so: example_relationship = validated_data.pop('example_relationship') instance = ExampleModel.objects.create(**validated_data) instance.example_relationship = example_relationship return instance The default implementation also does not handle nested relationships. If you want to support writable nested relationships you'll need to write an explicit `.create()` method. """ raise_errors_on_nested_writes("create", self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: for request_tenant in schema_handler( self.context["request"].tenant): instance = ModelClass._default_manager.create( **validated_data, tenant=request_tenant) except TypeError: tb = traceback.format_exc() msg = ("Got a `TypeError` when calling `%s.%s.create()`. " "This may be because you have a writable field on the " "serializer class that is not a valid argument to " "`%s.%s.create()`. You may need to make the field " "read-only, or override the %s.create() method to handle " "this correctly.\nOriginal exception was:\n %s" % ( ModelClass.__name__, ModelClass._default_manager.name, ModelClass.__name__, ModelClass._default_manager.name, self.__class__.__name__, tb, )) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): field = getattr(instance, field_name) field.set(value) return instance
def update(self, instance, validated_data): info = model_meta.get_field_info(instance) if validated_data.get('tags'): tags = validated_data.pop('tags') instance.tags = [] if len(tags) > 0: for tag in tags: new_tag, created = tag_model.objects.get_or_create( name=tag["name"]) instance.tags.add(new_tag) instance.save() # We shouldn't use this endpoint to update images. # This popping is just to make sure we dont get any internal errors # when clients sends images in update. if validated_data.get('images'): validated_data.pop('images') # We shouldn't use this endpoint to update comments. # This popping is just to make sure we dont get any internal errors # when clients sends comments in update. if validated_data.get('comments'): validated_data.pop('comments') # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don't need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def validate(self, attrs): # Since DRF 3.0 the model clean() method is no longer called # automatically. And this is probably not the best solution to do the # validation, but at the moment it's the simplest one. if self.instance is None: lsr = attrs.pop('lsr', None) instance = WebHookMapping(**attrs) instance.clean() if lsr is not None: attrs['lsr'] = lsr else: info = model_meta.get_field_info(self.instance) for attr, value in attrs.items(): if attr == 'lsr': # lsr can not be set directly at the moment, so skip it continue if attr in info.relations and info.relations[attr].to_many: # We don't have any to-many relations at the moment, but # this is to avoid setting them in the future if they are # added. Manipulating to-many relation directly changes # the DB so it can't be done here. continue else: setattr(self.instance, attr, value) self.instance.clean() return attrs
def _create(self, validated_data): validated_data = validated_data.copy() # We're going to pop stuff off here. # This logic is mostly copied from super().create(), # aside from the concrete_data stuff and `.clean()`ing the model before saving. ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) # Build a dict of fields we're guaranteed to be able to pass to `Model.create()` concrete_fields = set(info.fields) | set(info.forward_relations) concrete_data = { field: value for (field, value) in validated_data.items() if field in concrete_fields } instance = ModelClass(**concrete_data) instance.clean() instance.save() for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def create(self, validated_data): ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ( "Got a `TypeError` when calling `%s.objects.create()`. " "This may be because you have a writable field on the " "serializer class that is not a valid argument to " "`%s.objects.create()`. You may need to make the field " "read-only, or override the %s.create() method to handle " "this correctly.\nOriginal exception text was: %s." % (ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): if field_name == "permissions": value = self._get_or_create_permissons(value) setattr(instance, field_name, value) return instance
def create(self, validated_data): ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ("Got a `TypeError` when calling `%s.objects.create()`. " "This may be because you have a writable field on the " "serializer class that is not a valid argument to " "`%s.objects.create()`. You may need to make the field " "read-only, or override the %s.create() method to handle " "this correctly.\nOriginal exception text was: %s." % (ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc)) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def update(self, instance, validated_data): """ Default `.update()` method, but with overwritten friends' m2m assignment and return object annotation :param instance: :param validated_data: :return: """ raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) instance.save() for attr, value in m2m_fields: field = getattr(instance, attr) if attr == 'friends': field.set(value, through_defaults={'created_at': timezone.now()}) return self.context['view'].get_object()
def update(self, instance, validated_data): if 'params' in validated_data: answer_ids_new = [] answer_ids_pre = App_Param.objects.all().filter(app=instance).values_list('id', flat=True) for param in validated_data.pop('params'): isSet = param.pop("isSet",None) id = param.pop("id",None) param["app"]=instance ans, _created = App_Param.objects.update_or_create(id=id, defaults={**param}) # if _created: # ans.app = instance # ans.save() answer_ids_new.append(ans.id) delete_ids = set(answer_ids_pre) - set(answer_ids_new) App_Param.objects.filter(id__in=delete_ids).delete() from rest_framework.utils import model_meta info = model_meta.get_field_info(instance) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def _filter_fields_by_permissions(self, fields, permissions_kind): """ Filter serializer fields by permissions kind :param fields: serializer fields list :param permissions_kind: edit/view :return: fields allowed to interact with """ model = self.Meta.model targets_map = {Permission.get_target(model, field): field for field in fields} pk_fields = [] pk_target = Permission.get_target(model, 'pk') if pk_target in targets_map: pk_fields.append(targets_map.pop(pk_target)) pk_field = model_meta.get_field_info(model).pk pk_target = Permission.get_target(model, pk_field) if pk_target in targets_map: pk_fields.append(targets_map.pop(pk_target)) allowed_targets = Permission.apply_permissions(self.permissions, targets_map.keys(), permissions_kind) allowed_fields = list(map(lambda target: targets_map[target], allowed_targets)) if allowed_fields: allowed_fields.extend(pk_fields) return allowed_fields
def update(self, instance, validated_data): # print('查看auth_permissions:', validated_data.get('auth_permissions')) if validated_data.get('auth_permissions'): auth_permissions_data = validated_data.pop('auth_permissions') # 修改时创建权限菜单的方法 need_dels = AuthPermission.objects.filter(auth_id=instance.id) for item in need_dels: item.delete() for item in auth_permissions_data: # print('查看:', item) # print('查看id:', item.get('id')) AuthPermission.objects.create(auth=instance, **item) # 开多线程优化代码 # del_work = threading.Thread(target=del_worker,args=(need_dels,)) # del_work.start() # save_work = threading.Thread(target=save_worker,args=(instance,auth_permissions_data,)) # save_work.start() # save_work.join() # 继承自父类的方法 info = model_meta.get_field_info(instance) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def update_id_fields(self, fields): if 'id' not in self.fields: conf = get_ct(self.Meta.model).get_config() or {} lookup = conf.get('lookup', None) if lookup and lookup != 'id': fields['id'] = serializers.ReadOnlyField(source=lookup) info = model_meta.get_field_info(self.Meta.model) # In list views, remove [fieldname] as an attribute in favor of # [fieldname]_id. for name, field in info.forward_relations.items(): include = getattr(self.Meta, "fields", []) exclude = getattr(self.Meta, "exclude", []) if name in exclude or (include and name not in include): fields.pop(name, None) continue id_field, id_field_kwargs = self.build_relational_field( name, field) id_field_kwargs['source'] = name fields[name + '_id'] = id_field(**id_field_kwargs) if name in fields: # Update/remove DRF default foreign key field (w/o _id) if self.is_detail and isinstance(fields[name], serializers.Serializer): # Nested representation, keep for detail template context fields[name].read_only = True else: # Otherwise we don't need this field del fields[name] return fields
def create(self, validated_data): ModelClass = self.Meta.model # Remove to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ('Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % (ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc)) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if to_many: for field_name, value in to_many.items(): related_field = getattr(instance, field_name) for record in value: related_field.create(**record) return instance
def _configure_sideloads(self, meta): """ Assemble configuration for each sideload. """ self.sideloads = [] configs = [] for conf in getattr(meta, 'sideloads', []): assert isinstance(conf, tuple) and len(conf) >= 2 \ and len(conf) <= 3, ( '`Meta.sideloads` must be a list of tuples in the following ' 'format: (<model class>, <serializer class>, ' '<queryset instance (optional)>)' ) model, serializer = conf[:2] queryset = conf[0].objects.all() if (len(conf) == 2) else conf[2] configs.append((model, serializer, queryset)) relations = get_field_info(self.model).relations fields = self.base_serializer.fields.values() for field_name, info in relations.items(): try: conf = configs[[t[0] for t in configs].index(info.related)] except ValueError: continue field = fields[[f.source for f in fields].index(field_name)] key_name = getattr(conf[1].Meta, 'base_key', underscore(conf[0].__name__)) self.sideloads.append(Sideload( field=field, model=conf[0], serializer=conf[1], queryset=conf[2], key_name=pluralize(key_name) ))
def create(self, validated_data): ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError: tb = traceback.format_exc() msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception was:\n %s' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, tb ) ) raise TypeError(msg) if many_to_many: for field_name, value in many_to_many.items(): field = getattr(instance, field_name) field.set(value) return instance
def _validate_model_clean(self, attrs): """ Run validation using model's clean method. """ data = attrs.copy() ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the model initializer. info = model_meta.get_field_info(ModelClass) for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in data): data.pop(field_name) # remove tags field self._pop_tags(data) # self.instance is set in case of update (PATCH/PUT) call # otherwise create new model instance instance = self.instance or ModelClass() for k, v in data.items(): setattr(instance, k, v) try: instance.clean() except DjangoValidationError as e: raise self._django_validation_error_to_drf_validation_error(e) self._extra_instance_validation(instance)
def update_id_fields(self, fields): if 'id' not in self.fields: conf = get_ct(self.Meta.model).get_config() or {} lookup = conf.get('lookup', None) if lookup and lookup != 'id': fields['id'] = serializers.ReadOnlyField(source=lookup) info = model_meta.get_field_info(self.Meta.model) # In list views, remove [fieldname] as an attribute in favor of # [fieldname]_id. for name, field in info.forward_relations.items(): include = getattr(self.Meta, "fields", []) exclude = getattr(self.Meta, "exclude", []) if name in exclude or (include and name not in include): fields.pop(name, None) continue id_field, id_field_kwargs = self.build_relational_field( name, field ) id_field_kwargs['source'] = name fields[name + '_id'] = id_field(**id_field_kwargs) if name in fields: # Update/remove DRF default foreign key field (w/o _id) if self.is_detail and isinstance( fields[name], serializers.Serializer): # Nested representation, keep for detail template context fields[name].read_only = True else: # Otherwise we don't need this field del fields[name] return fields
def _get_filter_strings_for_model(model, depth, exclude=[]): info = model_meta.get_field_info(model) # Add base fields fields = info.fields.keys() if depth > 0: depth -= 1 # Add related fields one level deep for field_name, rel in info.relations.iteritems(): # Will recursively add related fields according to value of `depth` rel_field_names = [field_name + "__" + rel_field_name for rel_field_name in _get_filter_strings_for_model(rel.related_model, depth, exclude=exclude)] rel_field_names.append(field_name) fields += rel_field_names # filters_for_model is used by django_filters to determine # if fields we request to filter on are valid. So use that # to silently pass out any fields which django_filters will # complain about. all_filters = filters_for_model( model, fields=fields, exclude=exclude, filter_for_field=FilterSet.filter_for_field, filter_for_reverse_field=FilterSet.filter_for_reverse_field ) tmp_fields = [] for f in fields: if all_filters.get(f, False) == None: continue tmp_fields.append(f) fields = tmp_fields return fields
def _build_standard_proto_type(self, model_field): if model_field.one_to_one and model_field.primary_key: info = model_meta.get_field_info(model_field.related_model) return self.build_proto_type(info.pk.name, info, model_field.related_model) else: return self.type_mapping[model_field]
def _compare_objects(self, db_obj, api_obj, serializer=None): """ Compare two objects with each other based on the fields of the API serializer. """ serializer = serializer if serializer else self.serializer_cls() serializer_field_list = serializer.get_field_names( serializer._declared_fields, model_meta.get_field_info(self.model_cls) ) model_field_list = [f.name for f in self.model_cls._meta.get_fields()] for field in serializer_field_list: if field in model_field_list and not field: # Make sure the field is in the response. self.assertIn(field, api_obj) db_value = getattr(db_obj, field) api_value = api_obj.get(field) db_value = self._transform_value(db_value) if isinstance(db_value, Model) or isinstance(db_value, Manager): # Relationships can't be checked generically continue # Make sure the field value matches that of the factory object. self.assertEqual(api_value, db_value)
def _configure_sideloads(self, meta): """ Assemble configuration for each sideload. """ self.sideloads = [] configs = [] for conf in getattr(meta, 'sideloads', []): assert isinstance(conf, tuple) and len(conf) >= 2 \ and len(conf) <= 3, ( '`Meta.sideloads` must be a list of tuples in the following ' 'format: (<model class>, <serializer class>, ' '<queryset instance (optional)>)' ) model, serializer = conf[:2] queryset = conf[0].objects.all() if (len(conf) == 2) else conf[2] configs.append((model, serializer, queryset)) relations = get_field_info(self.model).relations fields = self.base_serializer.fields.values() for field_name, relation_info in relations.items(): try: related_model = compat.get_related_model(relation_info) conf = configs[[t[0] for t in configs].index(related_model)] except ValueError: continue field = fields[[f.source for f in fields].index(field_name)] key_name = getattr(conf[1].Meta, 'base_key', underscore(conf[0].__name__)) self.sideloads.append( Sideload(field=field, model=conf[0], serializer=conf[1], queryset=conf[2], key_name=pluralize(key_name)))
def create(self, validated_data): # To ensure caution, require nested_writes to be explicitly allowed if not (hasattr(self.Meta, "nested_writes") and self.Meta.nested_writes): raise_errors_on_nested_writes("create", self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): raise ValueError( "Many to many fields must be explicitly handled", field_name ) elif not relation_info.reverse and (field_name in validated_data): if not isinstance( validated_data[field_name], relation_info.related_model ): # Trying to set a foreign key but do not have the object, only the key validated_data[ relation_info.model_field.attname ] = validated_data.pop(field_name) instance = ModelClass(**validated_data) if hasattr(instance, "on_create") and callable(instance.on_create): instance.on_create() if not getattr(self, "parent", False): instance.save() return instance
def _writable_fields(self): fields = super(PermissionsBasedSerializerMixin, self)._writable_fields allowed_permissions = filter( lambda p: p.permission == self.Meta.permission_class.PERMISSIONS.edit and p.permission_type == self.Meta.permission_class.TYPES.allow, self.permissions ) disallowed_permissions = filter( lambda p: p.permission in [self.Meta.permission_class.PERMISSIONS.edit, self.Meta.permission_class.PERMISSIONS.view] and p.permission_type == self.Meta.permission_class.TYPES.disallow, self.permissions ) allowed_fields_names = [p.target.split('.')[-1] for p in allowed_permissions] disallowed_fields_names = [p.target.split('.')[-1] for p in disallowed_permissions] # PK allowed be default if allowed_fields_names: model = self.Meta.model info = model_meta.get_field_info(model) allowed_fields_names.extend(['pk', info.pk.name]) if '*' in allowed_fields_names: filtered_fields = fields else: filtered_fields = filter(lambda f: f.field_name in allowed_fields_names, fields) filtered_fields = filter(lambda f: f.field_name not in disallowed_fields_names, filtered_fields) return list(filtered_fields)
def _compare_objects(self, db_obj, api_obj, serializer=None): """ Compare two objects with eachother based on the fields of the API serializer. """ serializer = serializer if serializer else self.serializer_cls() serializer_field_list = serializer.get_field_names( serializer._declared_fields, model_meta.get_field_info(self.model_cls) ) model_field_list = self.model_cls._meta.get_all_field_names() for field in serializer_field_list: if field in model_field_list: # Make sure the field is in the response self.assertIn(field, api_obj) db_value = getattr(db_obj, field) api_value = api_obj.get(field) db_value = self._transform_value(db_value) if isinstance(db_value, Model) or isinstance(db_value, Manager): # Relationships can't be checked generically continue # Make sure the field value matches that of the factory object self.assertEqual(api_value, db_value)
def _create(self, validated_data): validated_data = validated_data.copy( ) # We're going to pop stuff off here. # This logic is mostly copied from super().create(), # aside from the concrete_data stuff and `.clean()`ing the model before saving. ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) # Build a dict of fields we're guaranteed to be able to pass to `Model.create()` concrete_fields = set(info.fields) | set(info.forward_relations) concrete_data = { field: value for (field, value) in validated_data.items() if field in concrete_fields } instance = ModelClass(**concrete_data) instance.clean() instance.save() for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def update(self, instance, validated_data): new_goods = [] if 'goods' in validated_data: ids_new = [] ids_pre = StudentGoods.objects.all().filter(student=instance).values_list('id', flat=True) for param in validated_data.pop('goods'): param["student"] = instance id = param.pop("id", None) ans, _created = StudentGoods.objects.update_or_create(id=id, defaults={**param}) ids_new.append(ans.id) new_goods.append(ans) delete_ids = set(ids_pre) - set(ids_new) StudentGoods.objects.filter(id__in=delete_ids).delete() from rest_framework.utils import model_meta info = model_meta.get_field_info(instance) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def update(self, instance: Employee, validated_data): raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don't need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) # instance.title='工程师' # setattr(instance,'title','工程师') with transaction.atomic(): instance.save() instance.salary.basesalary = validated_data.get('basesalary') instance.salary.titlesalary = validated_data.get('titlesalary') instance.salary.save() # Note that many-to-many fields are set after updating instance. # Setting m2m fields triggers signals which could potentially change # updated instance and we do not want it to collide with .update() for attr, value in m2m_fields: field = getattr(instance, attr) field.set(value) return instance
def pk_field(self): if self._pk_field: return self._pk_field assert hasattr(self, 'Meta'), ( 'Class {serializer_class} missing "Meta" attribute'.format( serializer_class=self.__class__.__name__)) assert hasattr(self.Meta, 'model'), ( 'Class {serializer_class} missing "Meta.model" attribute'.format( serializer_class=self.__class__.__name__)) if model_meta.is_abstract_model(self.Meta.model): raise ValueError( 'Cannot use ModelSerializer with Abstract Models.') model = self.Meta.model info = model_meta.get_field_info(model) if 'pk' in self.fields: self._pk_field = self.fields['pk'] return self._pk_field if info.pk.name in self.fields: self._pk_field = self.fields[info.pk.name] return self._pk_field assert False, 'Serializer {serializer_class} doesn\'t contain primary key field. ' \ 'Add `pk` or `{pk_name}` to fields attribute.'.format(serializer_class=self.__class__.__name__, pk_name=info.pk.name)
def update(self, instance, validated_data): if validated_data["order_pay"] == 3: # 待退款 time_obj = Time.objects.filter( time_id=validated_data["order_timeid"].time_id).first() time_obj.time_surplus += 1 time_obj.save() raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) instance.save() for attr, value in m2m_fields: field = getattr(instance, attr) field.set(value) return instance
def _map_model_field(self, model_field, direction): assert isinstance(model_field, models.Field) # to get a fully initialized serializer field we use DRF's own init logic try: field_cls, field_kwargs = serializers.ModelSerializer().build_field( field_name=model_field.name, info=get_field_info(model_field.model), model_class=model_field.model, nested_depth=0, ) field = field_cls(**field_kwargs) except: # noqa field = None # For some cases, the DRF init logic either breaks (custom field with internal type) or # the resulting field is underspecified with regards to the schema (ReadOnlyField). if field and not anyisinstance(field, [serializers.ReadOnlyField, serializers.ModelField]): return self._map_serializer_field(field, direction) elif isinstance(model_field, models.ForeignKey): return self._map_model_field(model_field.target_field, direction) elif hasattr(models, model_field.get_internal_type()): # be graceful when the model field is not explicitly mapped to a serializer internal_type = getattr(models, model_field.get_internal_type()) field_cls = serializers.ModelSerializer.serializer_field_mapping[internal_type] return self._map_serializer_field(field_cls(), direction) else: error( f'could not resolve model field "{model_field}". failed to resolve through ' f'serializer_field_mapping, get_internal_type(), or any override mechanism. ' f'defaulting to "string"' ) return build_basic_type(OpenApiTypes.STR)
def get_initial(self): """ Construct initial data for the serializer. Use the 'default' values specified by the django model definition """ initials = super().get_initial().copy() # Are we creating a new instance? if self.instance is None: ModelClass = self.Meta.model fields = model_meta.get_field_info(ModelClass) for field_name, field in fields.fields.items(): if field.has_default() and field_name not in initials: value = field.default # Account for callable functions if callable(value): try: value = value() except: continue initials[field_name] = value return initials
def equal_to_current(cls, json, fields_to_ignore=("id", "change_date", "changed_by")): """ Compares for equality this instance to a model instance constructed from the supplied JSON. This will ignore any fields in `fields_to_ignore`. Note that this method cannot handle fields with many-to-many associations, as those can only be set on a saved model instance (and saving the model instance will create a new entry). All many-to-many field entries will be removed before the equality comparison is done. Args: json: json representing an entry to compare fields_to_ignore: List of fields that should not be compared for equality. By default includes `id`, `change_date`, and `changed_by`. Returns: True if the checked fields are all equivalent, else False """ # Remove many-to-many relationships from json. # They require an instance to be already saved. info = model_meta.get_field_info(cls) for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in json): json.pop(field_name) new_instance = cls(**json) key_field_args = tuple( getattr(new_instance, key) for key in cls.KEY_FIELDS) current = cls.current(*key_field_args) # If current.id is None, no entry actually existed and the "current" method created it. if current.id is not None: return current.fields_equal(new_instance, fields_to_ignore) return False
def create(self, validated_data): """ We have a bit of extra checking around this in order to provide descriptive messages when something goes wrong, but this method is essentially just: return ExampleModel.objects.create(**validated_data) If there are many to many fields present on the instance then they cannot be set until the model is instantiated, in which case the implementation is like so: example_relationship = validated_data.pop('example_relationship') instance = ExampleModel.objects.create(**validated_data) instance.example_relationship = example_relationship return instance The default implementation also does not handle nested relationships. If you want to support writable nested relationships you'll need to write an explicit `.create()` method. """ raise_errors_on_nested_writes('create', self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc ) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def perform_update(self, serializer): ModelClass = serializer.Meta.model field_info = model_meta.get_field_info(ModelClass) kwargs = {} if 'last_updater' in field_info.relations: kwargs['last_updater'] = self.request.user instance = serializer.save(**kwargs) if self._should_log_activity(instance): self.make_log_activity(instance, CHANGE)
def update(self, instance, validated_data): #This is the same implementation as in ModelSerializer, but minus the assertion about nested writes! info = model_meta.get_field_info(instance) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: field = getattr(instance, attr) field.set(value) else: setattr(instance, attr, value) instance.save() return instance
def build_natural_key_fields(self): info = model_meta.get_field_info(self.Meta.model) fields = OrderedDict() for field, relation_info in info.relations.items(): if not issubclass(relation_info.related_model, NaturalKeyModel): continue field_class, field_kwargs = self.build_nested_field( field, relation_info, 1 ) fields[field] = field_class(**field_kwargs) return fields
def get_validators(self): # If the validators have been declared explicitly then use that. validators = getattr(getattr(self, 'Meta', None), 'validators', None) if validators is not None: return validators # Determine the default set of validators. validators = [] model_class = self.Meta.model field_names = set([ field.source for field in self.fields.values() if (field.source != '*') and ('.' not in field.source) ]) # Note that we make sure to check `unique_together` both on the # base model class, but also on any parent classes. for parent_class in [model_class] + list(model_class._meta.parents.keys()): for unique_together in parent_class._meta.unique_together: if field_names.issuperset(set(unique_together)): validator = UniqueTogetherValidator( queryset=parent_class._default_manager, fields=unique_together ) validators.append(validator) # Add any unique_for_date/unique_for_month/unique_for_year constraints. info = model_meta.get_field_info(model_class) for field_name, field in info.fields_and_pk.items(): if field.unique_for_date and field_name in field_names: validator = UniqueForDateValidator( queryset=model_class._default_manager, field=field_name, date_field=field.unique_for_date ) validators.append(validator) if field.unique_for_month and field_name in field_names: validator = UniqueForMonthValidator( queryset=model_class._default_manager, field=field_name, date_field=field.unique_for_month ) validators.append(validator) if field.unique_for_year and field_name in field_names: validator = UniqueForYearValidator( queryset=model_class._default_manager, field=field_name, date_field=field.unique_for_year ) validators.append(validator) return validators
def perform_create(self, serializer): ModelClass = serializer.Meta.model field_info = model_meta.get_field_info(ModelClass) kwargs = {} kwargs['creator'] = self.request.user if 'last_updater' in field_info.relations: kwargs['last_updater'] = self.request.user if 'project' in field_info.relations: kwargs['project'] = self.request.project instance = serializer.save(**kwargs) if self._should_log_activity(instance): self.make_log_activity(instance, ADDITION)
def create(self, validated_attrs): """ We have a bit of extra checking around this in order to provide descriptive messages when something goes wrong, but this method is essentially just: return ExampleModel.objects.create(**validated_attrs) If there are many to many fields present on the instance then they cannot be set until the model is instantiated, in which case the implementation is like so: example_relationship = validated_attrs.pop('example_relationship') instance = ExampleModel.objects.create(**validated_attrs) instance.example_relationship = example_relationship return instance The default implementation also does not handle nested relationships. If you want to support writable nested relationships you'll need to write an explicit `.create()` method. """ # Check that the user isn't trying to handle a writable nested field. # If we don't do this explicitly they'd likely get a confusing # error at the point of calling `Model.objects.create()`. assert not any( isinstance(field, BaseSerializer) and not field.read_only for field in self.fields.values() ), ( 'The `.create()` method does not suport nested writable fields ' 'by default. Write an explicit `.create()` method for serializer ' '`%s.%s`, or set `read_only=True` on nested serializer fields.' % (self.__class__.__module__, self.__class__.__name__) ) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_attrs. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_attrs): many_to_many[field_name] = validated_attrs.pop(field_name) instance = ModelClass.objects.create(**validated_attrs) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def create(self, validated_data): ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} # Save off the data for key, field in self.fields.items(): if isinstance(field, serializers.BaseSerializer): if isinstance(validated_data.get(key), list): # One-to-many... nested_data = validated_data.pop(key) many_to_many[key] = field.create(nested_data) elif isinstance(validated_data.get(key), dict): import pdb pdb.set_trace() # ForeignKey nested_data = validated_data.pop(key) if nested_data.get("id", empty) is empty: # we don't want to descend into creating objects, so throw a validation error # here and inform the user to create the related object before saving the # instance in operation raise ValidationError("Nested objects must exist prior to creating this parent instance.") else: # Update ChildClass = field.Meta.model try: child_instance = ChildClass.objects.get(pk=nested_data["id"]) except ChildClass.DoesNotExist: child_instance = field.create(nested_data) else: del nested_data["id"] child_instance = field.update(child_instance, nested_data) validated_data[key] = child_instance # Create the base instance instance = ModelClass.objects.create(**validated_data) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def create(self, validated_data): ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} # Save off the data for key, field in self.fields.items(): if isinstance(field, serializers.BaseSerializer): if isinstance(validated_data.get(key), list): # One-to-many... nested_data = validated_data.pop(key) many_to_many[key] = field.create(nested_data) elif isinstance(validated_data.get(key), dict): # ForeignKey nested_data = validated_data.pop(key) if nested_data.get("id", empty) is empty: # No id, so it looks like we've got a create... del nested_data["id"] child_instance = field.create(nested_data) else: # Update ChildClass = field.Meta.model try: child_instance = ChildClass.objects.get(pk=nested_data["id"]) except ChildClass.DoesNotExist: child_instance = field.create(nested_data) else: del nested_data["id"] child_instance = field.update(child_instance, nested_data) validated_data[key] = child_instance # Create the base instance instance = ModelClass.objects.create(**validated_data) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance
def get_fields(self): fields = super(NaturalKeyModelSerializer, self).get_fields() info = model_meta.get_field_info(self.Meta.model) for field in fields: if field not in info.relations: continue relation_info = info.relations[field] if not issubclass(relation_info.related_model, NaturalKeyModel): continue field_class, field_kwargs = self.build_nested_field( field, relation_info, 1 ) fields[field] = field_class(**field_kwargs) return fields
def create(self, validated_data): #This is the same implementation as in ModelSerializer, but minus the assertion about nested writes! ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) instance = ModelClass.objects.create(**validated_data) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): field = getattr(instance, field_name) field.set(value) return instance
def test_related_resource_collection(self): serializer = self._get_serializer_for_view( 'developer-detail', {'developer': self.developer1.pk}, ) field_info = get_field_info(Developer) relation_info = field_info.reverse_relations['programming_languages'] field_class, field_kwargs = serializer.build_relational_field( 'programming_languages', relation_info, ) self._assert_field_is_related_field(field_class) self._check_field_kwargs(field_kwargs, serializer, 'language-list')
def get_label_fields(self, default_fields): if not self.add_label_fields: return {} fields = {} exclude = getattr(self.Meta, 'exclude', []) if 'label' not in exclude and 'label' not in default_fields: fields['label'] = serializers.ReadOnlyField(source='__str__') info = model_meta.get_field_info(self.Meta.model) # Add labels for dates and fields with choices for name, field in info.fields.items(): if name in getattr(self.Meta, "exclude", []): continue if name + '_label' in default_fields: continue if isinstance(field, model_fields.DateTimeField): fields[name + '_label'] = LocalDateTimeField(source=name) if field.choices: fields[name + '_label'] = serializers.ReadOnlyField( source='get_%s_display' % name ) if isinstance(field, MODEL_BOOLEAN_FIELDS): fields[name + '_label'] = BooleanLabelField( source=name, ) # Add labels for related fields for name, field in info.forward_relations.items(): if name in getattr(self.Meta, "exclude", []): continue if name + '_label' in default_fields: continue f, field_kwargs = self.build_relational_field( name, info.forward_relations[name], ) label_field_kwargs = { 'source': name, } if field_kwargs.get('many', None): label_field_kwargs['many'] = field_kwargs['many'] fields[name + '_label'] = serializers.StringRelatedField( **label_field_kwargs ) return fields
def update(self, instance, validated_data): print("========== CALLED UPDATE! ===========") print(str(validated_data)) print(str(instance.id)) fields = ModelSerializer.get_fields(self) info = model_meta.get_field_info(instance) for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: print("setting multiple " + str(instance) + " " + str(attr) + " " + str(value)) set_many(instance, attr, value) else: print("setting single " + str(instance) + " " + str(attr) + " " + str(value)) setattr(instance, attr, value) instance.save() print(fields) return ModelSerializer.update(self, instance, validated_data)
def build_obj(self, data=None): """ Much of this is copied from the create method of ModelSerializer in DRF, as we need to be able to create an object using the serializer without saving it, which the create method does not allow. """ from rest_framework.utils import model_meta from rest_framework.serializers import raise_errors_on_nested_writes # Copy as validated_data is mutated if data == None: data = self.validated_data validated_data = copy.copy(data) raise_errors_on_nested_writes('create', self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass(**validated_data) except TypeError as exc: msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc ) ) raise TypeError(msg) return instance
def test_related_resource_with_no_common_ancestor(self): serializer = self._get_serializer_for_view( 'language-detail', { 'developer': self.programming_language1.author.pk, 'language': self.programming_language1.pk, }, ) field_info = get_field_info(ProgrammingLanguage) relation_info = field_info.forward_relations['website'] field_class, field_kwargs = \ serializer.build_relational_field('website', relation_info) self._assert_field_is_related_field(field_class) self._check_field_kwargs(field_kwargs, serializer, 'website-detail') self._check_lookup_url_kwarg_in_field_kwargs('website', field_kwargs)
def __new__(cls, name, bases, attrs): serializer_class = attrs.get('serializer_class', None) many_to_many_fields = many_to_one_fields = related_fields = [] info = None base_forward_rel = list(attrs.pop('_base_forward_rel', ())) for base in reversed(bases): if hasattr(base, '_base_forward_rel'): base_forward_rel.extend(list(base._base_forward_rel)) if serializer_class and issubclass(serializer_class, serializers.ModelSerializer): base_forward_rel.extend( list(getattr(serializer_class, '_related_fields', [])), ) many_to_many_fields.extend( list(getattr(serializer_class, '_many_to_many_fields', [])), ) many_to_one_fields.extend( list(getattr(serializer_class, '_many_to_one_fields', [])), ) if hasattr(serializer_class.Meta, 'model'): info = model_meta.get_field_info(serializer_class.Meta.model) meta_fields = list(serializer_class.Meta.fields) many_to_many_fields.extend(meta_fields) many_to_one_fields.extend(meta_fields) base_forward_rel.extend(meta_fields) if info is not None: many_to_many_fields = cls.get_many_to_many_rel(info, set(many_to_many_fields)) many_to_one_fields = cls.get_many_to_one_rel(info, set(many_to_one_fields)) related_fields = cls.get_forward_rel(info, set(base_forward_rel)) queryset = attrs.get('queryset', None) try: if queryset: if many_to_many_fields: queryset = queryset.prefetch_related( *normalize_prefetch_lookups(set(many_to_many_fields + many_to_one_fields)), ) if related_fields: queryset = queryset.select_related(*related_fields) attrs['queryset'] = queryset.all() except ProgrammingError: pass return super(OptimizeRelatedModelViewSetMetaclass, cls).__new__(cls, name, bases, attrs)
def create(self, validated_data): ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass(**validated_data) instance.set_password(validated_data['password']) instance.save() except TypeError as exc: msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc ) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) # All users are automatically added to the user group. # The user is created on boot so no try. group = Group.objects.get(name='user') instance.groups.add(group) return instance
def create(self, validated_data): ModelClass = self.Meta.model info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc ) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if self.validated_data['tags']: tag_list = [] for tag in self.validated_data['tags']: # tag_list.append(ContentTag.objects.get_or_create(tag_name=tag['tag_name'])[0]) tag_list.append(ContentTag.objects.get_or_create(tag_name=tag)[0]) setattr(instance, 'tags', tag_list) many_to_many.pop('tags') if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) instance.save() return instance
def create(self, validated_data): assert hasattr(self.Meta, "model"), 'Class {serializer_class} missing "Meta.model" attribute'.format( serializer_class=self.__class__.__name__ ) ModelClass = self.Meta.model model_info = model_meta.get_field_info(ModelClass) is_generic_relation = "content_type" in model_info.relations relations_data = {} m2m_relations_data = {} parents_query_dict = self.get_parents_query_dict() for parent_lookup in parents_query_dict: if parent_lookup in model_info.relations: parent_model = model_info.relations[parent_lookup].related_model parent_instance = get_object_or_404( parent_model, **{ (key.split("__", 1)[1] if "__" in key else "pk"): value for key, value in parents_query_dict.items() } ) if isinstance(model_info.relations[parent_lookup].model_field, ManyToManyField): m2m_relations_data[parent_lookup] = { "parent_instance": parent_instance, "related_name": model_info.relations[parent_lookup].model_field.name, "has_through_model": model_info.relations[parent_lookup].has_through_model, } elif is_generic_relation: relations_data["content_object"] = parent_instance else: parent_model_field = model_info.relations[parent_lookup].model_field relations_data[parent_lookup] = parent_instance validated_data.update(relations_data) instance = super(NestedSerializerMixin, self).create(validated_data) for parent_lookup in m2m_relations_data: if not m2m_relations_data[parent_lookup]["has_through_model"]: getattr(instance, m2m_relations_data[parent_lookup]["related_name"]).add( m2m_relations_data[parent_lookup]["parent_instance"] ) return instance