To add GET parameters in Swagger - django

Use django rest framework and django-rest-swagger in documentation of the methods it is not showing available GET parameters and the question is how can I set?
code:
# views.py
#api_view(['GET'])
def tests_api(request):
"""
:param request:
:return:
"""
id = request.query_params.get('id')
name = request.query_params.get('name')
return Response({'user': name, 'text': 'Hello world'})
# urls.py
urlpatterns = [
url(r"^api/v1/tests_api/$", tests_api),
]
http api:
GET https://127.0.0.1/api/v1/tests_api/?name=Denis&id=3
HTTP/1.1 200 OK
...
{
"user": "Denis",
"text": "Hello world"
}
now:
example:
Russian version.

Declare schema manually and specify fields there with location query
schema = ManualSchema(fields=[
coreapi.Field(
"name",
required=True,
location="query",
schema=coreschema.String()
),
]
)
See doc for details

urlpatterns = [
url(r'^api/v1/tests_api/(?P<id>\d+)/(?P<name>\w+)/$', tests_api),
]

Related

Django - Vue - how to generate view for tag, or?

Im use vue and django with rest framework. In django models.py i have model field "tahs" this is charfield with tags separated by comma.
exaple : django,forest,native
I want to generate view for each tag example "django".
OR
try to search in filed tahs and return objects contains this tag[ex.django]
this is my views.py
class TagsResultsViewSet(viewsets.ModelViewSet):
serializer_class = TagResultsViewSetSerializer
queryset = CustomUserPass.objects.all()
lookup_field = 'tahs'
def get_queryset(self, *args, **kwargs):
context = super().get_queryset(*args, **kwargs)
tag = self.kwargs['tahs']
print('this is tags:', tag)
context = self.queryset.filter(tahs__icontains=tag)
print(context)
return context
serializer.py
class TagResultsViewSetSerializer(serializers.ModelSerializer):
tahs = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = CustomUserPass
fields = '__all__'
urls.py
router = DefaultRouter()
...
router.register('tags', TagsPassViewSet, basename='tags')
router.register('tag', TagsResultsViewSet, basename='tag')
urlpatterns = [
path('', include(router.urls)),
]
vue file: TagView.vue
<template>
<div class="about">
<h1>You looking: {{tag}}</h1>
<div v-for="result in results" :key="result.id">
<div>{{result.username}}</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'TagView',
data() {
return {
results: [],
errors: [],
}
},
props: {
tag: {
type: String,
required: true,
},
},
mounted() {
this.getTagsResults()
},
methods: {
async getTagsResults() {
this.$store.commit('setIsLoading', true)
axios
.get(`/api/v1/tag/${this.tag}`)
.then(response => {
this.results = response.data
console.log(this.results)
})
.catch(error => {
console.log(error)
})
this.$store.commit('setIsLoading', false)
},
}
}
</script>
actually when i go to http://localhost:8080/tags/linux
i have output in console:
this is tags: linux
<QuerySet [<CustomUserPass: drw>, <CustomUserPass: user1>]>
Not Found: /api/v1/tag/linux/
[11/Apr/2022 11:26:44] "GET /api/v1/tag/linux/ HTTP/1.1" 404 23
edit
when i change in vue
.get(`/api/v1/tag/${this.tag}`)
to
.get('/api/v1/tag/')
and
in views
class TagsResultsViewSet(viewsets.ModelViewSet):
serializer_class = TagResultsViewSetSerializer
queryset = CustomUserPass.objects.all()
lookup_field = 'tahs'
def get_queryset(self, *args, **kwargs):
context = super().get_queryset(*args, **kwargs)
tag = 'django'
print('this is tags:', tag)
context = self.queryset.filter(tahs__icontains=tag)
print(context)
return context
it's render but tag = 'django' is hardcoded - for example.
and when i removed ${this.tag} from vue i my kwargs is empty.
and i cant do just tag = self.kwargs['tahs']
urls.py (app)
router = DefaultRouter()
router.register('navbar', NavbarPassViewSet, basename='navbar')
router.register('profile', ProfilePassViewSet, basename='profile')
router.register('profiles', ProfilesPassViewSet, basename='profiles')
router.register('online', ProfilesOnlineChecker, basename='online')
router.register('projects', PassProjectAll, basename='projects')
router.register('myproject', MyProjects, basename='myproject')
router.register('project', ProjectDetail, basename='project')
router.register('tags', TagsPassViewSet, basename='tags')
router.register('tag', TagsResultsViewSet, basename='tag')
urlpatterns = [
path('', include(router.urls)),
]
When you go to api/v1/tags/linux/ DRF does a queryset.get(tahs="linux").
This doesn't work with your data because your tahs field might contain other words. So you get a 404 error. You could almost fix this by setting the viewset lookup_field attribute to tahs__icontains however DRF expects that it will only get one result, where as you might have multiple instances of CustomUserPass that contain "linux". That's because api/v1/tags/linux is treated as a detail endpoint for a single instance by the viewset (that returns data for a single instance), not a list endpoint (that will return data for a list of instances).
What you really want to do is add a filter using the django-filters package (which also integrates well with DRF), and then perform your API query on the list endpoint like this: api/v1/tags/?tahs=linux (or similar).

Django "Method \"POST\" not allowed."

When I test my endpoints using Postman I get the message Method \"POST\" not allowed when I try to create a new truck using my createTruck function. I am able to delete, update, and get trucks from my database. Also I am able to create a new truck in Django Admin.
views.py
#api_view(['POST'])
def createTruck(request):
data = request.data
user = request.user
truck = Truck.objects.create(
user=user,
image=data['image'],
make=data['make'],
prototype=data['prototyp'],
year=data['year'],
serviceInterval=data['serviceInterval'],
nextService=data['nextService'],
seats=data['seats'],
bedLength=data['bedLength'],
color=data['color'],
vin=data['vin'],
currentMileage=data['currentMileage']
)
print("request", request)
serializer = TruckSerializer(truck, many=False)
return Response(serializer.data)
#api_view(['DELETE'])
def deleteTruck(request, pk):
truck = Truck.objects.get(id=pk)
truck.delete()
return Response('Truck Deleted!')
urls.py
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/<str:pk>/', views.getTruck, name="truck"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]
I might be passing in user wrong, but I'm not sure.
The Postman URL is
http://127.0.0.1:8000/api/trucks/create/
The body is
{
"user": 1,
"image": "/images/2016-tundra-600x400.jpeg",
"make": "Toyota",
"prototype": "Tundra",
"year": 2016,
"serviceInterval": 720,
"nextService": 600,
"seats": 2,
"bedLength": "5.50",
"color": "blak",
"vin": "0989098ad2321",
"currentMileage": 20000
}
Just change your URL pattern. It is a URL pattern conflict, that create-truck-pattern tries to get the truck with pk:create. The standard solution is to change the type of pk from str to int. But the other solution is to change URL priority in your patterns and move create-truck on step upper.
Recommended solution:
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/<int:pk>/', views.getTruck, name="truck"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]
other solution:
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/<str:pk>/', views.getTruck, name="truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]

Django elasticsearch dsl completion field issue

I am trying to implement search suggestions using django-elasticsearch-dsl-drf for streets.
This is documents.py:
class StreetDocument(Document):
id = fields.IntegerField()
name_ru = StringField(
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField(),
}
)
... # same for name_uz and name_oz
tags_ru = fields.CompletionField()
... # same for tags_uz and tags_oz
class Django:
model = Street
fields = (
'code',
'street_type'
)
in views.py I have this:
from django_elasticsearch_dsl_drf.constants import SUGGESTER_COMPLETION
from django_elasticsearch_dsl_drf.filter_backends import SuggesterFilterBackend, CompoundSearchFilterBackend
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
class SuggestionListAPI(DocumentViewSet):
document = StreetDocument
serializer_class = StreetDocumentSerializer
filter_backends = [
CompoundSearchFilterBackend,
SuggesterFilterBackend,
]
search_fields = (
'code',
'name_ru',
'name_uz',
'name_oz',
'tags_ru',
'tags_uz',
'tags_oz'
)
suggester_fields = {
'name_ru_suggest': {
'field': 'name_ru.suggest',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for name_uz and name_oz
'tags_ru': {
'field': 'tags_ru',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for tags_uz and tags_oz
}
Request to /suggestions?name_ru_suggest__completion=tolstoy does nothing, just receiving all streets unfiltered.
Request to /suggestions?search=tolstoy works great, but I need autocompletion.
Where did I go wrong? And that will be great if it's possible to use two fields for suggestion at once.
Thanks for your time and help.
It looks like you're using wrong endpoint for suggestions. Correct name is suggest.
Example: http://127.0.0.1:8000/search/publishers/suggest/?country_suggest__completion=Ar
Corresponding viewsets
As of your question about using two fields for suggestion at once, it's possible to get suggestions for multiple fields at once. For instance, if you have title and author, both properly indexed and configured, you could query it as follows:
http://127.0.0.1:8000/search/books/suggest/?title_suggest=wa&author_suggest=le
This will, however, return you the following result:
{
"title_suggest": [...],
"author_suggest": [...]
}
However, if you need to refine your suggestions based on some criteria, you could do that using context suggesters (search for it in the official documentation of django-elasticsearch-dsl-drf).

Django REST framework RetrieveAPIView gets empty "id" parameter and returns 404 error

I use Django 1.11 + Django REST Framework. I'm trying to get detailed data about distinct record using generic RetrieveAPIView from Django REST Framework and server returns "HTTP 404 Not Found" result, i.e. no data.
models.py file:
class TestPage( models.Model):
title = models.CharField( size = 120)
counter = models.IntegerField( default = 0)
def __unicode__(self):
return u'' + self.title
serializers.py file:
class TestPageSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'title', 'counter',)
model = models.TestPage
urls.py file:
urlpatterns = [
url( r'^admin/', admin.site.urls),
url( r'^api/', include( 'rest_framework.urls')),
url( r'^api/', include( 'testpages.urls')),
]
testpages/urls.py file:
urlpatterns = [
url( r'^$', views.TestPageList.as_view()),
url( r'^(?P<id>)\d+/$', views.TestPageDetail.as_view()),
]
and at last views.py file:
class TestPageList(generics.ListAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
class TestPageDetail(generics.RetrieveAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
# def get_object( self):
# print self.kwargs
If I request "http://127.0.0.1:8000/api/" for all records in the list, server returns all records. But if I want to get record by id using "http://127.0.0.1:8000/api/1/" for example, I get the empty result like in the photo below.
[![enter image description here][1]][1]
If I uncomment get_object()-function in last 2 lines, I can see {'id': u''} in terminal i.e. server gets empty parameter 'id'.
What wrong with this code?
The issue is your regular expression matching for id, you're putting \d+ outside of the matching group when it's actually what you want to match:
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view())
By the way, to be good REST citizen, you shouldn't add the / at the end of a URL for a specific object.
Note: As mentioned by JPG, adding a name for the resource you're fetching (plural) would be proper RESTful:
url(r'^pages/(?P<id>\d+)$', views.TestPageDetail.as_view())
This way, you fetch page nr. 1 at /api/pages/1
The original problem was the closing parathesis (the regex expression), you were misplaced that.
urlpatterns = [
url(r'^$', views.TestPageList.as_view()),
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view()),
^^^ here
]
Apart from that, You should change the urls patterns to a sensible form, as
#testpages/urls.py
urlpatterns = [
url(r'^test/$', views.TestPageList.as_view()),
url(r'^test/(?P<id>\d+)/$', views.TestPageDetail.as_view()),
]
then the list-api will be available on /api/test/ endpoint and the detail-api will be available on /api/test/{id}/

Show Filters and Ordering in Django Rest Framework Options Request

I'm using the Django Rest Framework I noticed on the web browseable part of the API there is a button called 'options' when clicked it shows the following...
HTTP 200 OK Vary: Accept Content-Type: text/html Allow: HEAD, GET, OPTIONS
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json",
"text/html"
],
"name": "Products",
"description": "API endpoint."
}
my question is, is there anyway I could list out here all the filter options an other stuff for this url?
You can make OPTIONS return whatever you want, by overriding the .metadata() method on the view.
See here: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L340
Update as of 2015: We now have a customizable metadata API that makes this easier: http://www.django-rest-framework.org/api-guide/metadata/
You can totally do this. Here's a custom metadata class that I've been keeping up to date here on StackOverflow. This simply lists all the available filters, their types, and their choices. It also lists the ordering fields that are available on a class:
class SimpleMetadataWithFilters(SimpleMetadata):
def determine_metadata(self, request, view):
metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view)
filters = OrderedDict()
if not hasattr(view, 'filter_class'):
# This is the API Root, which is not filtered.
return metadata
for filter_name, filter_type in view.filter_class.base_filters.items():
filter_parts = filter_name.split('__')
filter_name = filter_parts[0]
attrs = OrderedDict()
# Type
attrs['type'] = filter_type.__class__.__name__
# Lookup fields
if len(filter_parts) > 1:
# Has a lookup type (__gt, __lt, etc.)
lookup_type = filter_parts[1]
if filters.get(filter_name) is not None:
# We've done a filter with this name previously, just
# append the value.
attrs['lookup_types'] = filters[filter_name]['lookup_types']
attrs['lookup_types'].append(lookup_type)
else:
attrs['lookup_types'] = [lookup_type]
else:
# Exact match or RelatedFilter
if isinstance(filter_type, RelatedFilter):
model_name = (filter_type.filterset.Meta.model.
_meta.verbose_name_plural.title())
attrs['lookup_types'] = "See available filters for '%s'" % \
model_name
else:
attrs['lookup_types'] = ['exact']
# Do choices
choices = filter_type.extra.get('choices', False)
if choices:
attrs['choices'] = [
{
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
}
for choice_value, choice_name in choices
]
# Wrap up.
filters[filter_name] = attrs
metadata['filters'] = filters
if hasattr(view, 'ordering_fields'):
metadata['ordering'] = view.ordering_fields
return metadata
Put that somewhere in your project, then set your DEFAULT_METADATA_CLASS, and you should be all set, with a new key on your OPTIONS requests like so:
"filters": {
"sub_opinions": {
"type": "RelatedFilter"
},
"source": {
"type": "MultipleChoiceFilter",
"choices": [
{
"display_name": "court website",
"value": "C"
},
]
}
...more...
}
This will also display choices, mirroring the way it's handled elsewhere in DRF.