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

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}/

Related

django - No User matches the given query. Page Not found 404: User post list view breaks post detail view

I'm fairly new to django and trying to build a message board app. Everything's been running smoothly up until I tried to add functionality to display a list of posts by author. I got it working but then my post detail view started throwing a 'No User matches the given query. Page Not found 404' error and I can't seem to figure out why. Can anyone help?
views.py
class UserPostList(generic.ListView):
model = Post
# queryset = Post.objects.filter(status=1).order_by('-created_on')
template_name = 'user_posts.html'
paginate_by = 6
def get_queryset(self):
"""
Method to return posts restricted to 'published' status AND to
authorship by the user whose username is the parameter in the
url.
"""
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(
status=1, author=user
).order_by('-created_on')
class FullPost(View):
def get(self, request, slug, *args, **kwargs):
"""
Method to get post object.
"""
queryset = Post.objects.filter(status=1)
post = get_object_or_404(queryset, slug=slug)
comments = post.comments.order_by('created_on')
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
return render(
request,
"full_post.html",
{
"post": post,
"comments": comments,
"liked": liked
},
)
# I'll be adding a comment form in here too
urls.py
urlpatterns = [
...
path('<slug:slug>/', views.FullPost.as_view(), name='boards_post'),
...
path('<str:username>/', views.UserPostList.as_view(), name='user_posts'),
...
]
Error message
(When trying to view a single post (previously working) after adding the UserPostList view and route)
Using the URLconf defined in mhcmsgboard.urls, Django tried these URL patterns, in this order:
1. admin/
2. summernote/
3. register/ [name='register']
4. profile/ [name='profile']
5. login/ [name='login']
6. logout/ [name='logout']
7. new/ [name='create_post']
8. <slug:slug>/update/ [name='update_post']
9. <slug:slug>/delete/ [name='delete_post']
10. <str:username>/ [name='user_posts']
The current path, test-post/, matched the last one.
for <str:name> in path "-" not allowed to use in name,
when you use Slug both paths are equal.
urlpatterns = [
...
path('<slug:slug>/', views.FullPost.as_view(), name='boards_post'),
...
path('<slug:username>/', views.UserPostList.as_view(), name='user_posts'),
]
there are 2 simple ways
use sub path for one or both paths : user_post/<slug:username>/
Use re_path and define the regex to change path
exp:
re_path(r'^(?P\<username>\w+)/$', views.UserPostList.as_view()),
The problem is that you match update_post, delete_post and user_posts URL's to the root. As the error message explains, the current request is made against user_posts. And it seems that you don't have a user called test-post.
You could solve it e.g. with the following URL conf:
urlpatterns = [
...
path('board/<slug:slug>/', views.FullPost.as_view(), name='boards_post'),
...
path('posts/<str:username>/', views.UserPostList.as_view(), name='user_posts'),
...
]
That way, each path is unique and Django knows which View it has to call.

Testing a query parameter in django restframework

I am testing a query parameter in djnago restframework to retrieve the detail of an object. The query works in the browser but not the test. I think I am not calling the response correctly with:
response = self.client.get(reverse('quote-requests:get-calculator-detail', kwargs={'calculator_code': self.calc1.calculator_code}))
the apps urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('api/v1/quote-requests/', include('quote_requests.urls')),
]
which includes the quotes.urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from quote_requests import views
router = DefaultRouter()
router.register('get-calculator', views.CalculatorCodeDetailViewSet, basename='get-calculator')
app_name = 'quote-requests'
urlpatterns = [
path('', include(router.urls)),
]
The viewset is:
class CalculatorCodeDetailViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CalculatorCodeSerializer
lookup_field = ('calculator_code')
def get_queryset(self):
return (
Calculator.objects.filter(
calculator_code = self.request.query_params.get('calculator_code',)
)
)
The CalculatorCodeSerializer is:
class CalculatorCodeSerializer(serializers.ModelSerializer):
class Meta:
model = Calculator
fields = (
'name',
'calculator_code',
)
The test is:
def test_retrieving_calculator_detail_with_calculator_code(self):
''' test retrieving detail of a calculator '''
self.calc1 = Calculator.objects.create(
name = "Calculator 1000",
calculator_code = "HH1000",
)
response = self.client.get(reverse('quote-requests:get-calculator-detail', kwargs={'calculator_code': self.calc1.calculator_code}))
serializer = CalculatorCodeSerializer(self.calc1)
self.assertEqual(response.data, serializer.data)
This yields the error:
Traceback (most recent call last):
File "/app/quote_requests/tests/test_calculators_api.py", line 149, in test_retrieving_calculator_detail_with_calculator_code
self.assertEqual(response.data, serializer.data)
AssertionError: {'detail': ErrorDetail(string='Not found.',[14 chars]nd')} != {'name': 'Calculator 1000', 'calculator_cod[368 chars].25'}
When checking the browser link:
http://localhost:8000/api/v1/quote-requests/get-calculator/?calculator_code=HH1000
This works but test fails. Any help setting up the properly would be appreciated.
I think you are using the wrong endpoint name replace this "get-calculator-detail" with this "get-calculator"
def test_retrieving_calculator_detail_with_calculator_code(self):
# test retrieving detail of a calculator
self.calc1 = Calculator.objects.create(
name = "Calculator 1000",
calculator_code = "HH1000",
)
response = self.client.get(reverse('quote-requests:get-
calculator', kwargs={'calculator_code':
self.calc1.calculator_code}))
serializer = CalculatorCodeSerializer(self.calc1)
self.assertEqual(response.data, serializer.data)
With the help of others on Fiverr I have gotten the test to pass. I hesitate to call it answered but I am putting the detail here in case those who know a better way can comment.
TLDR - the response variable was not written correctly either the reverse does not do what I want or it cannot be done with reverse. Either way the change that worked was:
response = self.client.get('/api/v1/quote-requests/get-calculator/', {'calculator_code': self.calc1.calculator_code})
This does not return an iterable object. It returns an OrderDict which looks like this:
[{"name":"Calculator 1000","calculator_code":"HH1000","id":7,}]
I used print(response.content) to check this.
so you need to use the json function to pull the data out and match it to serializer.data - the response from the serializer.
response.data = response.json()[0]
this pulls the "[]" off and formats it like this:
{'name': 'Calculator 1000', 'calculator_code': 'HH1000', 'id': 7, }
The [0] on the end gets the object (I think) and returns it in json format.
The two fiverr users that helped me with this were:
https://www.fiverr.com/tailongk
and
https://www.fiverr.com/asipita

Django optional URL captured value for rest APIView

For following Django code using django_rest_framework:
class PollMessageView(generics.ListAPIView):
serializer_class = MessageSerializer
lookup_url_kwarg = 'count'
def get_queryset(self):
count = self.kwargs.get(self.lookup_url_kwarg)
queryset = Message.objects.all()[:count]
return queryset
urlpatterns = [
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
How do I make the count parameter optional in the URL pattern? For example, if I visit /poll_message/ without a count, it would still call the PollMessageView (and I can set a default number for count if it's missing)?
create a new path as ,
urlpatterns = [
path('poll_message/', PollMessageView.as_view(), name="poll_message-wo-count"),
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
# URLconf
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.page),
path('blog/page<int:num>/', views.page),
]
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...
In detail reference - https://docs.djangoproject.com/en/3.0/topics/http/urls/#specifying-defaults-for-view-arguments
You can specify defaults for view arguments, but the example on the website is for functional view, not sure how would that work for class based view or in this case, the rest framework class based view.
However, you can add another path with extra options to achieve the same thing
path('poll_message/', PollMessageView.as_view(), {
"count": 5
}, name="poll_message"),

Django Rest Framework Reverse() Method Fails

My main urls.py is located here ahlami -> ahlami -> urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/accounts/', include('accounts.api.urls')),
]
My accounts app urls.py is located here ahlami -> accounts -> api -> urls.py
urlpatterns = [
path('users/<int:pk>/', views.UserView.as_view(), name='user-detail')
]
One of my accounts views.py returns
token = Token.objects.create(......)
return Response(data=AnonymousUserTokenResponseSerializer(instance=token).data)
My token model has three fields only. For simplicity, I listed one field below
class Token(rest_framework.authtoken.models.Token):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE
AnonymousUserTokenResponseSerializer is linked to the Token model and returns three json attributes
class AnonymousUserTokenResponseSerializer(serializers.ModelSerializer):
user_id = serializers.ReadOnlyField(source='user.id')
user_url = reverse(viewname='user-detail')
class Meta:
model = Token
fields = ('key',
'user_id',
'user_url')
AnonymousUserTokenResponseSerializer fails because it can't identify reverse()
user_url = reverse(viewname='user-detail')
python manage.py runserver throws the error below because of the line above
django.core.exceptions.ImproperlyConfigured: The included URLconf
'ahlami.urls' does not appear to have any patterns in it. If you see
valid patterns in the file then the issue is probably caused by a
circular import.
My settings is located here ahlami -> ahlami -> settings -> base.py and base.py has this
ROOT_URLCONF = 'ahlami.urls'
I expect to get an output that looks like but couldn't because of the error above.
{
"key": "891e388399f2fcae016fe6887107034239041478",
"user_id": 29,
"user_url": http://localhost/api/accounts/users/29
}
How can I resolve this error and make reverse() work?
django.core.exceptions.ImproperlyConfigured: The included URLconf
'ahlami.urls' does not appear to have any patterns in it. If you see
valid patterns in the file then the issue is probably caused by a
circular import.
Use serializers.HyperlinkedIdentityField
class AnonymousUserTokenResponseSerializer(serializers.ModelSerializer):
user_id = serializers.ReadOnlyField(source='user.id')
user_url = serializers.HyperlinkedIdentityField(
view_name='user-detail',
source='user.id',
lookup_field='pk'
)
class Meta:
model = Token
fields = ('key', 'user_id', 'user_url')

ForeignKeyAutocompleteAdmin

Given these two models:
class Product(Model):
upc = CharField(max_length=96, unique=True)
...
class Meta:
app_label = 'scrapers'
class Order(Model):
...
product = ForeignKey('scrapers.Product', related_name='orders', on_delete=models.CASCADE)
objects = OrderManager()
class Meta:
app_label = 'scrapers'
And this admin.py:
class OrderAdmin(ForeignKeyAutocompleteAdmin):
related_search_fields = {
'product': ('upc', 'retailer_offerings__name')
}
fields = ('product', 'client', 'completed', 'expires', 'amount', 'filled')
admin.site.register(Order, OrderAdmin)
Having done collectstatic and declared django_extensions and my app in INSTALLED_APPS. Why am I getting this:
[04/Dec/2016 05:54:28] "GET /admin/scrapers/product/foreignkey_autocomplete/?search_fields=upc&app_label=scrapers&model_name=product&q=045496 HTTP/1.1" 302 0
Not Found: /admin/scrapers/product/foreignkey_autocomplete/change/
[04/Dec/2016 05:54:28] "GET /admin/scrapers/product/foreignkey_autocomplete/change/ HTTP/1.1" 404 1875
On input to the input field (box to the left not pk input to the right)?
The Product table has millions of rows and the default admin configuration doesn't handle this well so I tried the extensions package solution. The widget is requesting product/foreignkey_autocomplete but a quick grep through manage.py show_urls shows that only /orders has been registered with the foreignkeyautocomplete package. I don't see anything in the docs addressing url configuration (I assume this is done when registering with the admin). How can I get this to work?
Partial solution:
after examining the urls and realizing it was attempting top send the search queries to /product/foreignkey_autocomplete/... I tried to create an empty admin for that model as well. It worked but the behavior is still odd. It seems to stop searching after 4-5 characters have been entered and doesn't bother refreshing.
as per my update, adding a ForeignKeyAutocompleteAdmin for the other side of the relation created the missing urls and the functionality seems to work
#Verbal_Kint I think I may have figured the same issue as yours.
I have a InlineModelAdmin "TaskInline" for model Task, a field "SCRIPT" of it is a foreign-key to model TestScript (managed by "TestAdmin").
After I made sure the related model's ModelAdmin (here for me it's TestAdmin) inherits ForeignKeyAutoCompleteAdmin instead of admin.ModelAdmin, then made sure TestAdmin had an method wrap as below:
class TestAdmin(ForeignKeyAutocompleteAdmin):
ForeignKeyAutocompleteAdmin.model = TestScript
def wrap(self, view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = super(TestAdmin, self).get_urls()
urlpatterns.insert(0, url(r'^(.+)/run/', self.wrap(self.run_view), name='%s_%s_run' % info))
urlpatterns.insert(0, url(r'^add/$', self.wrap(self.add_view), name='%s_%s_add' % info))
urlpatterns.insert(0, url(r'^add_to_template_mission/$', self.wrap(self.add_to_template_mission_view), name='%s_%s_add_to_template_mission' % info))
urlpatterns.insert(0, url(r'^add_to_mission/$', self.wrap(self.add_to_mission_view), name='%s_%s_add_to_mission' % info))
urlpatterns.insert(0, url(r'^$', self.wrap(self.changelist_view), name='%s_%s_changelist' % info))
return urlpatterns
class TaskInline(ForeignKeyAutocompleteTabularInline):
model = Task
related_search_fields = {
'SCRIPT': ('FILENAME', 'FILE_PATH', 'FILE_CONTENT', ),
}
And, don't forget to have
urlpatterns = super(TestAdmin, self).get_urls()
in get_urls() inside TestAdmin
Then, everything got working fine.
Maybe there is a better way, but this did solve my problem. Hope this can help.