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

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"),
]

Related

Django rest elasticsearch filter range in url query params

I am using elasticsearch with django rest framework. I am using this lib:
https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/
I am trying to filter by price range according to these docs: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/
This is my views:
class TestAPIView(DocumentViewSet):
document = TestDocument
serializer_class = TestSerializer
queryset = TestModel.objects.all()
filter_backends = [
FilteringFilterBackend
]
filter_fields = {
'price': {
'field': 'price',
'lookups': [
LOOKUP_FILTER_RANGE,
LOOKUP_QUERY_IN,
],
},
}
and this is my document.py file
#registry.register_document
class TestDocument(Document):
price = fields.IntegerField(attr='price')
class Index:
name = 'TestModel'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = TestModel
fields = [
'id',
]
When I hit on browser this url: http://127.0.0.1:8000/search/api/v1/test-model/?price=12 It works very well, even when I try with this url : http://127.0.0.1:8000/search/api/v1/test-model/?price=55 it works,
I am facing a problem to filter by range, like I want to filter price 12 to price 90, in this case how can I pass query parameter for the range? can anyone help me in this case?
The source codeĀ [GitHub] provides an example for this:
# Example: {"query": {"range": {"age": {"gte": "16", "lte": "67"}}}}
# Example: http://localhost:8000/api/users/?age__range=16__67
You thus can filter with:
http://127.0.0.1:8000/search/api/v1/test-model/?price__range=12__90
to retrieve items with a price between 12 and 90.

Django filtering for URLs and VIEWS - "Page not found at /collection/..."

I am retruning all records for specific object in Django successfully.
My url.py is path('city/', views.TestListCity.as_view())
From postman I just GET: http://192.168.99.100:8080/collection/city and it returns all records.
Example:
{
"id": 3,
"name": "Bor",
"region": {
"id": 2,
"name": "Sun"
}
},
Now I want to filter records with column name.
I tried this:
urls.py
path('city/(?P<name>.+)/$', views.TestListCity.as_view()),
views.py
class TestListCity(generics.RetrieveAPIView):
serializer_class = TestListCitySerializer
def get_queryset(self):
name = self.kwargs['name']
return City.objects.filter(name=name)
I try GET:
http://192.168.99.100:8080/collection/city?name=Bor
But then 404:
<title>Page not found at /collection/city</title>
I also tried second approach:
urls.py
path('city/<str:name>/', views.TestListCity.as_view())
views.py
class TestListCity(generics.RetrieveAPIView):
serializer_class = TestListCitySerializer
queryset = City.objects.all()
lookup_field = 'name'
But exactly the same response.
If you want to use the below URL (or its equivalent) where the string is a part of the url matching:
path('city/<str:name>/$', views.TestListCity.as_view()),
Then just call:
http://192.168.99.100:8080/collection/city/Bor/
Instead of:
http://192.168.99.100:8080/collection/city?name=Bor
The later one passes Bor as the value of the name parameter of the GET request, i.e. you will need to use something like name = self.request.GET.get('name') to get the name parameter. It will not be a part of the URL matching.
It's because you used path(), and you wanted a regular expression (re_path()).
Change this:
from django.urls import path
path('city/(?P<name>.+)/$', views.TestListCity.as_view()),
to:
from django.urls import re_path
re_path('city/(?P<name>.+)/$', views.TestListCity.as_view()),
This should work. And maybe the / could be optional, like this: 'city/(?P<name>.+)/?$'

Django 1.11 - make the whole site read-only for users from a given group (preferably with a fixture)

My question is not the same with View permissions in Django because that one explains how to deal with the issue in Django 2.1 and higher which has a concept of "view" permission while I am working with Django 1.1. which does not.
Django 1.11
I have a group users who should have only read-only access to everything on the site. No restrictions on fields, models, and actial data, only what they can do with it (read-only). I know about possible implementations that suggest doing it "field-by-field" (or "make all fields read-only") and "model-by-model" solution. I am curios if there is a way to do it cleaner, on user group level, or at least on user level.
My views.py so far is default:
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
"""View function for home page of site."""
# Render the HTML template index.html with the data in the context variable
return render(request, 'home.html')
Ideally, I'd like to be able to do this with a fixture.
Currently in the fixture, I have my groups defined like this:
{
"model": "auth.group",
"fields": {
"name": "some_group",
"permissions": [
[
"add_somemodel",
"myproject",
"somemodel"
],
[
"change_somemodel",
"myproject",
"somemodel"
],
[
"delete_somemodel",
"myproject",
"somemodel"
]
]
}
}
In Django 2.2 I can do
{
"model": "auth.group",
"fields": {
"name": "some_group",
"permissions": [
[
"view_somemodel",
"myproject",
"somemodel"
]
]
}
}
but in Django 1.11 I have only "add", "delete" and "change" - no "view" option (according to the docs enter link description here). So, is there a way to create a fixture that creates a group that has only read permissions for everything?
In your view you need something like this(Notice that this is an example of how to view a post if you belong to the proper access group):
def post_detail(request, slug=None):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
instance = get_object_or_404(Post, slug=slug)
share_string = quote_plus(instance.content)
context = {
"title": instance.title,
"instance": instance,
"share_string": share_string,
}
return render(request, "post_detail.html", context)
Pay attention to:
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
Here are some link to the docs that will help you:
How to authenticate users
All attributes to django.contrib.auth
Edit:
I saw your code now, so what you want to achieve can be done like this
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
"""View function for home page of site."""
# With that way although a user might be logged in
# but the user might not have access to see the page
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
# Render the HTML template index.html with the data in the context variable
return render(request, 'home.html')
That way a user may be logged in, but if it's not staff member or superuser it want have access to the page.
Thank you everybody for responding. I did not figure out the way to do it with only user/group/permissions config in the db with Django 1.11, maybe it does not exist. Here is what I ended up with (very similar to the first suggestion I hit on SO when I started the research 4 hours ago, with minimal code changes)
Create a fixture for my new group that contains only "change_somemodel" permission and created a user as a member of that group, i.e. no "add_somemodel" and no "delete_somemodel" entries and load it into DB:
[
{
"model": "auth.group",
"fields": {
"name": "<my_new_group>",
"permissions": [
[
"change_<somemodel1>",
"<myproject>",
"<somemodel1>"
],
[
"change_<somemodel2>",
"<myproject>",
"<somemodel2>"
]
]
}
,
{
"model": "auth.user",
"fields": {
"password": "<my_password>",
"last_login": null,
"is_superuser": false,
"username": "<my_username>",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2019-04-01T14:40:30.249Z",
"groups": [
[
"<my_new_group>"
]
],
"user_permissions": []
}
}
],
This took care of the first part: now when I login as this user I do not have "Add new.." or "Delete" buttons anywhere for any model for my user.
Now, when I load a view for a given instance of some model, I still have the fields editable and I still see "Save", "Save and Add Another" and "Save and Continue" buttons. To take care of this, in admin.py in the superclass from which all my models are subclassed, in its custom def changeform_view I added:
if request.user.groups.filter(name='<my_new_group>'):
extra_context['show_save_and_add_another'] = False
extra_context['show_save_and_continue'] = False
extra_context['show_save'] = False
This made those 3 "Save" buttons disappear for all the models and made all fields read-only. Now this new user can not add, delete or edit anything for any model yet they can see everything, just as I wanted.
Looks like in the newer Django, starting from 2.1, this can be done even better.

To add GET parameters in Swagger

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),
]

Using views to change DRF response

I would like to alter the response from an API.
However, it does not alter the result properly. I get a KeyError: 'game'.
I am not sure why, as my API response (via URL) seems to have the value game in it. I may be getting confused with the JSON response, and the python object.
I have a sample of the API response below
results from API
{
"pk": 995,
"game": [
{
"name": "Finance",
"gamelevel": 3
},
{
"name": "Data",
"gamelevel": 1
}
]
},
views.py
class TagList(viewsets.ModelViewSet):
queryset = Task.objects.filter(game__isnull=False).all()
serializer_class = TagSortSerializer
def get_queryset(self):
test = self.queryset.values('title', 'game__name')
result = defaultdict(set)
for item in queryset:
parent = {'name': 'NoLevel_1'}
children = []
for game in item['game']:
if game['gamelevel'] == 1:
parent = game
else:
children.append((game['gamelevel'], game['name']))
result[parent['name']].update(children)
result = [
{
'name': parent,
'game_child': [
{'name': name, 'gamelevel': gamelevel} for gamelevel, name in games
],
'gamelevel': 1,
} for parent, games in result.items()
]
return result
You're using the values queryset method to get only a selection of fields from the model, and the only fields you've specified are title and tag__name. So you won't get game or any of the other keys you've used.
You certainly don't want to use values here in the first place; just do a normal query and access fields via dot lookup rather than dictionary.