django rest framework: forms how to pass the select options - django

I am trying to create a form in reactjs as frontend, which will create an object using Django api
I am trying to create an ingredient
the following is the serializer to create or update and ingredient
class IngredientCreateUpdateSerializer(ModelSerializer):
class Meta:
model = Ingredient
fields = [
'name',
'munit',
'rate',
'typeofingredient',
]
I will be having munit and typeofingredient as select fields.
The options for these select fields have to supplied from the server.
eg: munit can have options of kg,ltr, pcs etc
and typeofingredient can have option of vegetables, spices, fruits etc
both are ForeignKey type.
So prior to using the create api i have to supply to the form the options of both munit and typeofingredient at that particular instance from the server.
So how to do this. For getting the options should i have to create another api. or is there any direct way

If both typeofingredient and munit are foreign keys then you can define serializers for each of those models and use list api to populate select options.
If you want to combine both Serializers in a single api, you can do the same thing in the ViewSet.
views.py
#Assuming MunitOptionsSerializer and TypeOfIngredientOptionsSerializer are
#your serializers
class IngredientOptionsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
def list(self, request):
# assuming MunitOptions & TypeOfIngredientOptions are the models
qs = MunitOptions.objects.all()
s1 = MunitOptionsSerializer(qs, many=True)
qs = TypeOfIngredientOptions.objects.all()
s2 = TypeOfIngredientOptionsSerializer(qs, many=True)
return Response({'munit':s1.data, 'typeofingredient':s2.data})
In your app urls.py
options_list = OptionsViewSet.as_view({
'get': 'list',
})
router = routers.DefaultRouter()
urlpatterns = patterns(
url(r'^', include(router.urls)),
url(r'^options/$', options_list, name='options-list'),
)
Also make 'read only' restriction at ViewSet by inheriting them from ReadOnlyModelViewSet instead of ModelViewSet, since you will be using those serializers only for list/retrieve functions.

Related

How to modify router and viewsets for template rendering

I have a simply CRUD application with Rest-Framework
my views, serializers and routers is created dynamically.
I used a simple code from DRF docs like that:
VIEWS:
class PersonViewSet(viewsets.ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
SERIALIZERS:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
URL:
router = routers.SimpleRouter()
router.register(r'persons', PersonViewSet)
Now I have to do smth for displaying data for end-users.
My goal is:
1. Play 1-2 weeks with build-in django functionality for frontend development
2. Consider about vue.js as a frontend framework.
So I have to use django and DRF as a backend (this is for sure).
I have some trouble with template rendering.
Now I create a simple HTML templates and try to implement them.
There are some problems when using them with ViewSets. Seems that I have to use another parent class for views.
Could you provide me some examples how should I change my views and router?
I already tried to use simply APIView, but seems that routers works only with viewsets.
Now I create a simple HTML templates and try to implement them. There are some problems when using them with ViewSets. Seems that I have to use another parent class for views.
Views
DRF defaults to rendering responses as either JSON (rest_framework.renderers.JSONRenderer) or BrowsableApi (rest_framework.renderers.BrowsableAPIRenderer). [source]
In order to render HTML from within DRF APIView (and it's subclasses) you have to set TemplateHTMLRenderer as one of renderer_classes in your class: [source]
class PersonDetail(generics.RetrieveAPIView):
queryset = Person.objects.all()
renderer_classes = [TemplateHTMLRenderer]
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return Response({'person': self.object}, template_name='person_detail.html')
Routers only work with ViewSets. To expose your custom view on an endpoint you have to append it into urlpatterns in your urls file: [source]
router = routers.SimpleRouter()
router.register(r'persons', PersonViewSet)
urlpatterns = [
url(r'^person_detail/$', PersonDetail.as_view()),
]
urlpatterns += router.urls

django-rest-swagger can't seem to work for me. I can't get it to document anything beyond a title

It seems like django-rest-swagger dropped support for the YAML documentation, and replaced it with a vague non-documented way to do things. I've spent the last 48 hours trying to understand how I can have it document the parameters that go into my post methods.
For instance: I have this:
class user_addresses(APIView):
"""
get all addresses or post a new one
"""
authentication_classes = ([JSONWebTokenAuthentication])
def get(self, request, format=None):
addresses = Address.objects.filter(owner_id=request.user.id)
print (addresses)
serializer = address_serializer(addresses, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = address_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'success': True,
'result': serializer.validated_data},
status=status.HTTP_201_CREATED)
return Response({'success': False,
'result': serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
But the django-rest-swagger will show it as:
Can someone point me in the direction of something that works where I can define all the rich data that swagger allows, like the post field names, if they're mandatory or not. etc. i'm just going crazy here running in circle and can't find anything but complaints that there's no way to do this.
So the idea with the 2.0 update was to use CoreAPI, the "internal" rest framework schema generation, and from it generate the swagger spec.
CoreAPI uses serializer and view classes to do its thing. From serializers it knows what fields are required, what type are those fields and if you want to add your personal description you can do so with help_text parameter:
some_field = serializers.Field(help_text='Field description')
In your case, the problem will be that it won't be able to understand the relationship between the APIView and your serializer. I suggest to take an extra step and move to generic views or viewsets, all of them support serializer_class attribute that can be used for the introspection. For your example something like this should work:
# serializer
class AddressSerializer(serializers.ModelSerializer):
line1 = serializers.CharField(help_text='Field documentation!')
class Meta:
model = Address
fields = '__all__'
read_only_fields = 'owner',
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
# api class-based view
class UserAddresses(generics.ListCreateAPIView):
"""
General API documentation (not wisible in the swagger view)
get:
GET-specific documentation!
Lorem ipsum
post:
POST-specific documentation!
Dolor **sit amet**
"""
authentication_classes = ([JSONWebTokenAuthentication])
permission_classes = permissions.IsAuthenticated,
serializer_class = AddressSerializer
def get_queryset(self):
return Address.objects.filter(owner_id=self.request.user.id)
For views there is a specific docstirng format, it's very simple and hopefully, will improve overtime. In any way, you should have a bit more acceptable result now:
A CoreAPI Document can help you make a custom Swagger view. Swagger takes a coreapi json input to render the view - Django Rest Swagger uses the Python bindings of CoreAPI to generate that JSON (https://github.com/core-api/python-client).
What does the coreapi.Document object contain?
For each API, you can create a coreapi.Link() object.
Each Link object contains:
A URL
HTTP Method
Description
Fields
The list of fields must contain coreapi.Field() objects. A Field object has the parameters:
Name
Required (whether the field is mandatory)
Location (path parameter or query parameter)
Description
An Example
A sample Swagger Schema would look something like this, if we were to use CoreAPI:
import coreapi
def api_schema_generator():
api_schema = coreapi.Document(
title="My Swagger",
content={
"User Addresses": {
"int_api_get": coreapi.Link(
url="/int_api/v1/addresses/",
action="get",
description="Get addresses of a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user whose addresses are to be found"
),
]
),
"int_api_post": coreapi.Link(
url="/int_api/v1/addresses/",
action="post",
description="Add address for a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user"
),
coreapi.Field(
name="address",
required=True,
location="path",
description="Address of the user"
),
]
)
}
}
)
return api_schema
Our view would take this coreapi.Document object as input. We use the SwaggerUIRenderer, OpenAPIRenderer and CoreJSONRenderer decorators for our view.
views.py:
from rest_framework.decorators import api_view, renderer_classes
from rest_framework_swagger import renderers as swagger_renderer
from rest_framework import renderers
#api_view()
#renderer_classes([renderers.CoreJSONRenderer,
swagger_renderer.OpenAPIRenderer,
swagger_renderer.SwaggerUIRenderer,
])
def schema_view(request):
api_schema = api_schema_generator()
return response.Response(api_schema)
All that we need now is a URL mapping for our view.
urls.py:
from django.conf.urls import include, url
urlpatterns = [
url(r'^$', views.schema_view),
]
Writing a custom swagger might seem slightly tedious, but you have complete control over what data you want to expose in your Swagger View.

Django rest framework- Only see's one view

I'm creating 4 different views for my API. However, Django Rest Framework only see's one API/URL.
The last project in my views is always the one that appears with DRF. EG if I remove "ProjectViewSet" from my views, "Location" will appear as the URL at DRF.
This screenshot provides info:
My views
class DataViewSet(viewsets.ModelViewSet):
queryset = Task.objects.exlude(Q(tag=1)|Q(name=1))
serializer_class = TaskSerializer
class EventViewSet(viewsets.ModelViewSet):
queryset = Task.objects.exlude(Q(tag=2)|Q(name=2))
serializer_class = TaskSerializer
class LocationViewSet(viewsets.ModelViewSet):
queryset = Task.objects.exlude(Q(tag=3)|Q(name=3))
serializer_class = TaskSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Task.objects.exlude(Q(tag=4)|Q(name=4))
serializer_class = TaskSerializer
My URLS (again):
router = routers.DefaultRouter()
router.register(r'Tag', TagViewSet)
router.register(r'Info', InfoViewSet)
router.register(r'Data', DataViewSet)
router.register(r'Friends', FriendsViewSet)
urlpatterns = router.urls
urlpatterns += [
url(r'^1.1/tag/', rest_views.TagView.as_view()),
url(r'^1.1/task/', rest_views.TaskView.as_view()),
]
Found the solution. As my serializers share the same data model, DRF seems to be getting stuck trying to automatically discover the url naming pattern.
Giving a base_name argument to each of my models solved the issue!
router.register(r'Data', DataViewSet, base_name='Data')
router.register(r'Friends', FriendsViewSet, base_name='Friends')

how to use django rest filtering with mongoengine

Hi I am starting django 1.8.3 with mongodb using mongo engine to create rest api.
I am using rest_framework_mongoengine to do so.
I wanted to use a feature of DjangoFilterBackend for same.
My code is:
models.py:
from mongoengine import *
from django.conf import settings
connect(settings.DBNAME)
class Client(Document):
name = StringField(max_length=50)
city = StringField(max_length=50)
country = StringField(max_length=200, verbose_name="Country")
address = StringField(default='')
Serializer.py
from client.models import Client
from rest_framework_mongoengine.serializers import DocumentSerializer
class ClientSerializer(DocumentSerializer):
class Meta:
model = Client
depth = 1
views.py
from rest_framework_mongoengine.generics import *
from rest_framework import filters
class ClientList(ListCreateAPIView):
serializer_class = ClientSerializer
queryset = Client.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('name',)
I start getting error
QuerySet object has no attribute model
Don't know where its going wrong. If I remove filter_field It works but I can not use filter feature.
Any help would be of great use
You can also perform the filtering by overriding the get_queryset() method and creating a generic filtering function.
Here, we specify the filter fields tuple in the view as my_filter_fields on which we want to perform filtering. Then in our get_queryset(), we call a function get_kwargs_for_filtering().
get_kwargs_for_filtering() functions iterates over the fields defined in my_filter_fields and checks if this was passed in the query_params. If the field is found then a key with the field name and value as the retrieved value is set in a dictionary filtering_kwargs. After the iteration is over, this filtering_kwargs dictionary is returned to the get_queryset() method.
This filtering_kwargs dictionary is used to filter the queryset then.
from rest_framework_mongoengine.generics import *
from rest_framework import filters
class ClientList(ListCreateAPIView):
serializer_class = ClientSerializer
my_filter_fields = ('name', 'country') # specify the fields on which you want to filter
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
for field in self.my_filter_fields: # iterate over the filter fields
field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
if field_value:
# filtering_kwargs[field] = field_value
field = self.get_serializer().fields[field_name]
filtering_kwargs[field] = field.to_representation(field_value)
return filtering_kwargs
def get_queryset(self):
queryset = Client.objects.all()
filtering_kwargs = self.get_kwargs_for_filtering() # get the fields with values for filtering
if filtering_kwargs
queryset = Client.objects.filter(**filtering_kwargs) # filter the queryset based on 'filtering_kwargs'
return queryset
here is a good method to do this work.
firstly, you should install django_mongoengine_filter rest_framework_mongoengine
pip install django_mongoengine_filter
pip install rest_framework_mongoengine
secondly, you can write your owner Filter like this:
import django_mongoengine_filter as filters
from app.models import User
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ['name']
finally, you can use the UserFilter like this in view class:
from app.models import User
from app.serializer import UserS # use rest_framework_mongoengine to write serializer
from app.filters import UserFilter
class UserVS(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserS
# override filter_queryset function
def filter_queryset(self, queryset):
filter = UserFilter(self.request.query_params, queryset=queryset)
return filter.qs
Although the question is referring to Django 1.8. This is a question that is still important and valid in later versions of Django, DRF and MongoEngine.
At the time of this answer I am running
Django==4.1.3
djangorestframework==3.14
django-rest-framework-mongoengine==3.4.1
mongoengine==0.24.2
django-filter==22.1
# patched version of django-mongoengine-filter to support Django 4.0
# https://github.com/oussjarrousse/django-mongoengine-filter
# Pull request https://github.com/barseghyanartur/django-mongoengine-filter/pull/16
django-mongoengine-filter>=0.3.5
The idea in this answer is to add filtering support to django-rest-framework-mongoengine using django-mongoengine-filter that is an replacement or an extension to django-filter and should work the same way as django-filter.
First let's edit the project/settings.py file. Find the INSTALLED_APPS variable and make sure the following "Django apps" are added:
# in settings.py:
INSTALLED_APPS = [
# ...,
"rest_framework",
"rest_framework_mongoengine",
"django_filters",
# ...,
]
the app django_filters is required to add classes related to filtering infrastructure, and other things including html templates for DRF.
Then in the variable REST_FRAMEWORK we need to edit the values associated with the key: DEFAULT_FILTER_BACKENDS
# in settings.py:
REST_FRAMEWORK = {
# ...
"DEFAULT_FILTER_BACKENDS": [
"filters.DjangoMongoEngineFilterBackend",
# ...
],
# ...
}
DjangoMongoEngineFilterBackend is a custom built filter backend that we need to add to the folder (depending on how you structure your project) in the file filters
# in filters.py:
from django_filters.rest_framework.backends import DjangoFilterBackend
class DjangoMongoEngineFilterBackend(DjangoFilterBackend):
# filterset_base = django_mongoengine_filter.FilterSet
"""
Patching the DjangoFilterBackend to allow for MongoEngine support
"""
def get_filterset_class(self, view, queryset=None):
"""
Return the `FilterSet` class used to filter the queryset.
"""
filterset_class = getattr(view, "filterset_class", None)
filterset_fields = getattr(view, "filterset_fields", None)
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
element = queryset.first()
if element:
queryset_model = element.__class__
assert issubclass(
queryset_model, filterset_model
), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
str(queryset_model),
)
return filterset_class
if filterset_fields and queryset is not None:
MetaBase = getattr(self.filterset_base, "Meta", object)
element = queryset.first()
if element:
queryset_model = element.__class__
class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
model = queryset_model
fields = filterset_fields
return AutoFilterSet
return None
This custom filter backend will not raise the exceptions that the original django-filter filter backend would raise. The django-filter DjangoFilterBackend access the key model in QuerySet as in queryset.model, however that key does not exist in MongoEngine.
Maybe making it available in MongoEngine should be considered:
https://github.com/MongoEngine/mongoengine/issues/2707
https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues/294
Now we can add a custom filter to the ViewSet:
# in views.py
from rest_framework_mongoengine.viewsets import ModelViewSet
class MyModelViewSet(ModelViewSet):
serializer_class = MyModelSerializer
filter_fields = ["a_string_field", "a_boolean_field"]
filterset_class = MyModelFilter
def get_queryset(self):
queryset = MyModel.objects.all()
return queryset
Finally let's get back to filters.py and add the MyModelFilter
# in filters.py
from django_mongoengine_filter import FilterSet, StringField, BooleanField
class MyModelFilter(FilterSet):
"""
MyModelFilter is a FilterSet that is designed to work with the django-filter.
However the original django-mongoengine-filter is outdated and is causing some troubles
with Django>=4.0.
"""
class Meta:
model = MyModel
fields = [
"a_string_field",
"a_boolean_field",
]
a_string_field = StringFilter()
a_boolean_field = BooleanFilter()
That should do the trick.

How to apply a filter backend to all fields of all resources in Django Rest Framework?

I have a lot of resources and i want to apply the DjangoFilterBackend to all of them.
Tried setting in settings.py
'DEFAULT_FILTER_BACKENDS': [
'rest_framework.filters.DjangoFilterBackend',
]
But it didn't work
I tried adding only filter_backends = (filters.DjangoFilterBackend,) to one of my resources and it still didn't work.
Only after I explicitly added filter_fields = ('col1','col2',) it started working with those fields only.
Is there any way I can apply the filter backend to all resources and all fields (same way i do with permissions for example ... ) ?
Thanks.
Right now you are telling Django REST Framework to use the DjangoFilterBackend for all views, but you are not telling it how the FilterSet should be generated.
django-filter will automatically generate a FilterSet for all of the fields on a model if the fields are set to None. Django REST Framework will automatically generate a FilterSet if filter_fields are not set to None, which means you won't be able to use the default DjangoFilterBackend.
You can create a custom DjangoFilterBackend though, which will automatically generate the FilterSet for all fields on the model.
from rest_framework.filters import DjangoFilterBackend
class AllDjangoFilterBackend(DjangoFilterBackend):
"""
A filter backend that uses django-filter.
"""
def get_filter_class(self, view, queryset=None):
"""
Return the django-filters `FilterSet` used to filter the queryset.
"""
filter_class = getattr(view, 'filter_class', None)
filter_fields = getattr(view, 'filter_fields', None)
if filter_class or filter_fields:
return super(AllDjangoFilterBackend, self).get_filter_class(self, view, queryset)
class AutoFilterSet(self.default_filter_set):
class Meta:
model = queryset.model
fields = None
return AutoFilterSet
This will still use the original filter backend for situations where the view defines a custom filter_class or filter_fields, but it will generate a custom FilterSet for all other situations. Keep in mind that you shouldn't allow fields which aren't returned through the API to be filtered, as you are opening yourself up to future security issues (like people filtering a user list by passwords).
Ok I know it was a long time ago, but I was just faced with this question today (2019/11), so I decided to share this way that I think it is a little better:
Just use '__all__' for filter fields
filter_fields = '__all__'
Kevin Browns answer is fantastic, however may be slightly out of date now.
Running the AllDjangoFilterBackend filter backend with django-filter==2.1.0 throws the following:
Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' has been deprecated since 0.15.0 and is now disallowed. Add an explicit 'Meta.fields' or 'Meta.exclude' to the AutoFilterSet class.
It seems that simply replacing fields = None with exclude = '' is sufficient to use all fields. Full code below:
from django_filters.rest_framework import DjangoFilterBackend
class AllDjangoFilterBackend(DjangoFilterBackend):
'''
Filters DRF views by any of the objects properties.
'''
def get_filter_class(self, view, queryset=None):
'''
Return the django-filters `FilterSet` used to filter the queryset.
'''
filter_class = getattr(view, 'filter_class', None)
filter_fields = getattr(view, 'filter_fields', None)
if filter_class or filter_fields:
return super().get_filter_class(self, view, queryset)
class AutoFilterSet(self.default_filter_set):
class Meta:
exclude = ''
model = queryset.model
return AutoFilterSet
Save this to your_project/your_app/filters.py (or similar) and then ensure your settings file contains:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'your_project.your_app.filters.AllDjangoFilterBackend'
),
}
Updated version of Stuart Buckingham comment for django-rest-framework 3.13.1:
from django_filters.rest_framework import DjangoFilterBackend
class AllDjangoFilterBackend(DjangoFilterBackend):
"""
A filter backend that uses django-filter.
"""
def get_filterset_class(self, view, queryset=None):
'''
Return the django-filters `FilterSet` used to filter the queryset.
'''
filter_class = getattr(view, 'filter_class', None)
filter_fields = getattr(view, 'filter_fields', None)
if filter_class or filter_fields:
return super().get_filter_class(self, view, queryset)
class AutoFilterSet(self.filterset_base):
class Meta:
fields = "__all__"
model = queryset.model
return AutoFilterSet