I have a use case, where the request required fields are different depending on one of the field values of the request.
For example, if the value of the movable type in the request is 'P', then some fields are mandatory, otherwise, if the value of the move type is 'D', then some of the other fields are mandatory.
How to create a custom request for such a use case using drf-yasg ?
Based on what I found in drf_yasg docs you need to implement a concept called Inspector classes to customize behavior related to a specific field, serializer, filter, or paginator classes you can implement the FieldInspector, SerializerInspector, FilterInspector, PaginatorInspector classes and use them with #swagger_auto_schema or one of the related settings.
Here is an example that is a FieldInspector that removes the title attribute from all generated Schema objects and taken from Inspector classes [drf_yasg-docs] :
from drf_yasg.inspectors import FieldInspector
class NoSchemaTitleInspector(FieldInspector):
def process_result(self, result, method_name, obj, **kwargs):
# remove the `title` attribute of all Schema objects
if isinstance(result, openapi.Schema.OR_REF):
# traverse any references and alter the Schema object in place
schema = openapi.resolve_ref(result, self.components)
schema.pop('title', None)
# no ``return schema`` here, because it would mean we always generate
# an inline `object` instead of a definition reference
# return back the same object that we got - i.e. a reference if we got >a reference
return result
class NoTitleAutoSchema(SwaggerAutoSchema):
field_inspectors = [NoSchemaTitleInspector] + >swagger_settings.DEFAULT_FIELD_INSPECTORS
class ArticleViewSet(viewsets.ModelViewSet):
swagger_schema = NoTitleAutoSchema
...
Related
I've made an API in flask and MongoDB (MongoEngine) and it's admin panel through flask-admin. I'm looking for a way to disable flask-admin validation for a field which is essentially list of references field.
For example:
class A(db.Document):
pid = db.IntField(unique=True)
Bs = db.ListField(db.ReferenceField(B, dbref=False, reverse_delete_rule=NULLIFY))
Here class A has a list field for references of class B. Model View for class A
class A(ModelView):
can_create = True
can_delete = True
can_edit = True
def is_accessible(self):
return current_user.has_role("admin")
Now if I try to create a new document of class A from flask-admin, it doesn't allow saying Invalid Choice. I can create a class A object only when I choose at least one B object to be referred in A.Bs. But I want to disable this validation. I want to create class A object even when initially there are no B's object are referenced.
The Code is correct and working through API. It's just flask-admin validation which is causing problem on initially creating objects of class A without linking to objects of class B.
This is possible through API but I'm finding no method to do it via admin panel. How can I disable this default validation only for this field initially or there is another method which is better?
Complete DRF beginner here... I'm confused about the following concepts:
Let's say I POST some data, including a complex JSON blob for one of the fields, in order to create an object. Where should I actually create this object? Looking at the 3.1 docs, it seems like two places are equally valid for this: Serializer.create() and ViewSet.create(). How do I decide where to create my object and which way is considered "canonical"?
I understand that I need to run Serializer.is_valid() in order to validate the POSTed data. However, what is the difference between .data and .validated_data? They appear to be the same.
Finally, what is the "canonical" way to use a JSONField (e.g. django-jsonfield, but I'm not married to this package/implementation)? I have a model with several JSONFields and would like to use it "correctly" in DRF. I am aware of https://stackoverflow.com/a/28200902/585783, but it doesn't seem enough.
EDIT: My use case is an API POST that includes a complex JSON blob in one of the fields. I need to parse the JSON field, validate it, get/create several objects based on it, link new and existing objects, and finally store the JSON field in one of the new objects. So, I need to do custom validation for this JSON field by parsing it to python:
from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
class MySerializer(serializers.ModelSerializer):
my_json_field = JSONSerializerField()
def validate_my_json_field(self, value):
stream = BytesIO(value)
list_of_dicts = JSONParser().parse(stream)
# do lots of validation to list_of_dicts
# render list_of_dicts back to a JSON string
return validated_list_of_dicts_as_json
Now, depending on which way I choose in Concept 1, I have to parse the validated JSON again to create my objects in create(), which doesn't feel right.
Thanks in advance!
The contents of HTTP requests (POST, GET, PUT, DELETE) will always be processed by the views (View, APIView, generic views, viewsets). The serializers are just part of how these views process the requests. Serializers are the "means" to connect the View layer with the Model layer. For what serializers do specifically, please read the first paragraph of the this page of the official docs.
To answer #1: you almost always do not need to touch either unless you have a very specific use case. In those extraordinary cases:
You override Serializer.create() if you have to customize how model
instances are converted into native Python objects and vice versa. (e.g. create multiple objects)
You override ViewSet.create() if you need to customize how the actual request itself will be processed. (e.g. if there is an additional query parameter in the request, add some response headers)
To answer #2, you almost never need to use is_valid() when using generic views or ViewSets. They already do it under the hood for you. The serializer's .data and .validated_data are a bit tricky to explain. The former contains the Python datatype representation of the queryset/model instances you want to serialize, while the latter is the result of the validation process involved in checking if a Python object conforms to that particular Python datatype representation mentioned earlier, which in turn can be converted into a model instance. If that did not make sense, refer to Serializing objects and Deserializing objects.
As for #3, what do you mean by JSON field? As far as I know, Django does not have a model field called JSONField. Is this from a third party package or your own custom written model field? If so, then you will probably have to find or write a package that will let you integrate it with DRF smoothly and "correctly" whatever that means.
EDIT
Your use case is too complicated. I can only give you rough code for this one.
class MyModelSerializer(serializers.ModelSerializer):
my_json_field = JSONSerializerField()
class Meta:
model = MyModel
def validate(self, data):
# Get JSON blob
json_blob = data['my_json_field']
# Implement your own JSON blob cleanup method
# Return None if invalid
json_blob = clean_json_blob(json_blob)
# Raise HTTP 400 with your custom error message if not valid
if not json_blob:
raise serializers.ValidationError({'error': 'Your error message'})
# Reassign if you made changes and return
data['my_json_field'] = json_blob
return data
def create(self, validated_data):
json_blob = validated_data['my_json_field']
# Implement your object creation here
create_all_other_objects_from_json(json_blob)
# ...
# Then return a MyModel instance
return my_model
How can you check on save if the object was changed by the user? I.e. if any differences from the original database object were introduced. Before it was possible with pre_save() (See object changes in post_save in django rest framework), but now that was replaced with perform_update, which no longer holds both objects (original and modified) for comparison.
In Django REST Framework 3, pre_save was replaced with perform_update, which only takes the serializer as an argument (instead of the object itself).
You can access the validated data that was passed into the request using the .validated_data attribute on the serializer. This is the recommended replacement for .object, and should allow you to determine what the differences are.
def perform_update(self, serializer):
original_object = self.get_object() # or (the private attribute) serializer.instance
changes = serializer.validated_data
serializer.save(attr=changed_value) # pass arguments into `save` to override changes
I have a model with a custom json serializer that performs some processing prior to dumping to json.
Now, when fetching a single obj i want to use the custom serializer from the model to fetch the entire object (with the processing mentioned above). When fetching a list i want to use the default serializer to fetch only the headers (render only the model fields).
I looked into three options
overriding obj_get
def obj_get(self, bundle, **kwargs):
obj = ComplexModel.objects.get(pk=kwargs['pk'])
return obj.to_serializable()
i got thrown with
{"error": "The object LONG JSON DUMP has an empty attribute 'description' and doesn't allow a default or null value."}
not sure why this is happening - the field description is nullable, Plus - why tastypie is checking validation for objects already in the database, and... while fetching ??
using dehydrate
def dehydrate(self, bundle):
return bundle.obj.to_serializable()
This is great but the cycle is executed before each object - so i cann't tell if I'm fetching a list or a single object. The result here is the full serizliazed objects whether it's a list or a single entry.
creating a custom serializer
class CustomComplexSerializer(Serializer):
def to_json(self, data, options=None):
if isinstance(data,ComplexModel):
data = data.to_serializable()
return super(CustomComplexSerializer,self).to_json(data)
Same problem here, when fetching one entry the serializer accepts the obj in data.obj, when it's fetching a list it accepts a dict (odd...). I can check if bundle is an instance of dict as well - but testing for the type of ComplexModel felt awkward enough.
So what is the best way to implement a custom serialization for fetching only a single entry ?
Just for future reference, I think i found the right way to do this and it's by using full_dehydrate.
def full_dehydrate(self, bundle, for_list=False):
if not for_list:
return bundle.obj.to_serializable()
return super(ReportResource,self).full_dehydrate(bundle,for_list)
I get data in from POST and validate it via this standard snippet:
entry_formset = EntryFormSet(request.POST, request.FILES, prefix='entries')
if entry_formset.is_valid():
....
The EntryFormSet modelform overrides a foreign key field widget to present a text field. That way, the user can enter an existing key (suggested via an Ajax live search), or enter a new key, which will be seamlessly added.
I use this try-except block to test if the object exists already, and if it doesn't, I add it.
entity_name = request.POST['entries-0-entity']
try:
entity = Entity.objects.get(name=entity_name)
except Entity.DoesNotExist:
entity = Entity(name=entity_name)
entity.slug = slugify(entity.name)
entity.save()
However, I now need to get that entity back into the entry_formset. It thinks that entries-0-entity is a string (that's how it came in); how can I directly access that value of the entry_formset and get it to take the object reference instead?
I would suggest writing a helper factory function for your form set so that you can customize the display widget according to the data. Something like the following:
def make_entry_formset(initial_obj=None, custom_widget=forms.Textarea):
# these will be passed as keyword arguments to the ModelChoiceField
field_kwargs={'widget': custom_widget,
'queryset': Entity.objects.all()}
if initial_obj is not None:
field_kwargs.update({'initial': initial_obj})
class _EntryForm(forms.ModelForm):
entity = forms.ModelChoiceField(**field_kwargs)
class Meta:
model = Entry
return modelformset_factory(Entry, form=_EntryForm)
Then in your view code you can specify the widget you want and whether to bind to an initial Entity object. For the initial rendering of the formset, where you just want a Textarea widget and no initial choice, you can use this:
formset_class = make_entry_formset(custom_widget=forms.Textarea)
entry_formset = formset_class()
Then if you want to render it again (after the is_valid() block) with the Entity object already defined, you can use this:
formset_class = make_entry_formset(initial_obj=entity,
custom_widget=forms.HiddenInput)
entry_formset = formset_class(request.POST, request.FILES)
You can use any widget you like, of course, but using a HiddenInput would prevent the end user from interacting with this field (which you seem to want to bind to the entity variable you looked up).