I've done what feels like the absolute minimum in setting up a rest endpoint in front of a django model, but I cannot post to it from the browseable API.
On djangorestframework==3.3.0, all posts to /api/transactions/ are rejected.
models.py
class TransactionQuerySet(models.QuerySet):
...
class Transaction(models.Model):
objects = TransactionQuerySet.as_manager()
id = models.UUIDField(primary_key=True,
editable=False,
default=uuid4,
unique=True)
description = models.CharField(max_length=120)
timestamp = models.DateTimeField(default=get_timestamp,
editable=False)
amount = models.DecimalField(max_digits=8, decimal_places=2)
def __unicode__(self):
return '{0} ({1})'.format(self.description,
to_decimal(self.amount))
class Meta:
ordering = ['-timestamp']
serializers.py
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transaction
exclude = ()
views.py
class TransactionViewSet(viewsets.ModelViewSet):
queryset = Transaction.objects.all()
serializer_class = TransactionSerializer
tracking.urls
from rest_framework.routers import DefaultRouter
from django.conf.urls import url
import views
router = DefaultRouter()
router.register(r'transactions', views.TransactionViewSet)
urlpatterns = router.urls
urls.py
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^api/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api/', include('tracking.urls')),
]
Submitting the form gets this response
HTTP 400 Bad Request
Content-Type: application/json
Vary: Accept
Allow: GET, POST, HEAD, OPTIONS
{
"amount": [
"This field is required."
],
"description": [
"This field is required."
]
}
Am I missing something?
I can post to the endpoint with authentication disabled. Apparently, it is related to this bug in DRF
Related
I need to get just one product object in my VeiwSet based on a given slug, I looked into the docs, but I can't find any solution to this problem. I need to get the slug from the url path aswell, but I don't know how to do it too. Obviously the code below doesn't work.
product/serializers.py
from rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.ModelSerializer):
image = serializers.ImageField(required=True)
class Meta:
model = Product
fields = ("name", "description", "slug", "id", "price", "stock", "image", "category")
product/views.py
from django.http.response import JsonResponse
from rest_framework import serializers, viewsets
from rest_framework.response import Response
from django.http import JsonResponse
from .serializers import ProductSerializer
from .models import Product
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all().order_by('name')
serializer_class = ProductSerializer
class ProductDetailViewSet(viewsets.ViewSet, slug):
queryset = Product.objects.filter(slug=slug)
serializer_class = ProductSerializer
product/urls.py
from rest_framework import routers, urlpatterns
from django.urls import path, include
from .views import ProductViewSet, ProductDetailiewSet
router = routers.DefaultRouter()
router.register(r'', ProductViewSet)
urlpatterns = [
path('<str:slug>/',),
path('', include(router.urls))
]
ViewSets allows you to combine ListView and DetailView into one view.
So you don't need two different views to handle the both actions.
Now if you want to use slug in the url instead of the id by default, you just have to specify lookup_field in the serializer and the view like this :
serializers.py
class ProductSerializer(serializers.ModelSerializer):
image = serializers.ImageField(required=True)
class Meta:
model = Product
fields = ("name", "description", "slug", "id", "price", "stock", "image", "category")
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
views.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all().order_by('name')
serializer_class = ProductSerializer
lookup_field = 'slug'
urls.py
router = routers.DefaultRouter()
router.register(r'', ProductViewSet)
urlpatterns = [
url(r'', include(router.urls)),
]
Now you can query http://localhost:8000/ for products list and http://localhost:8000/product_slug for product detail with product_slug as slug.
More about Django ViewSets and Routers
I'm missing something, but I don't know what it is. When I go to the DRF Viewer, alerts is not listed in the possible list of urls. all the other Rest URLs do.
here's my serializer.py:
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Options
fields = '__all__'
validators = [
UniqueTogetherValidator(
queryset=Options.objects.all(),
fields=('Member', 'skey', 'Time_Period')
)
]
api.py:
class OptionViewSet(generics.ListCreateAPIView):
serializer_class = OptionSerializer
def get_queryset(self):
"""
This view should return a list of all the options
for the currently authenticated user.
"""
user = self.request.user
return Options.objects.filter(Member=user)
and my urls.py:
router = routers.DefaultRouter()
router.register(r'users', api.UserViewSet)
router.register(r'groups', api.GroupViewSet)
router.register(r'currency', api.BitCoinViewSet)
router.register(r'latest_prices', api.CurrencyLatestViewSet)
router.register(r'options', api.OptionViewSet.as_view, 'alerts')
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Why does the alert url not show up? Thanks.
Routers only work with ViewSets, but your OptionViewSet is an ordinary APIView.
You should be able to fix it by just using the appropriate mixins and base class:
class OptionViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = OptionSerializer
def get_queryset(self):
"""
This view should return a list of all the options
for the currently authenticated user.
"""
user = self.request.user
return Options.objects.filter(Member=user)
I followed the qblog tutorial, using python 2.7.10 and django 1.9.5.
When i enter the admin's blog interface, and clicked add blog entry, then it shown me below:
TemplateDoesNotExist at /admin/blog/entry/add/
django_markdown/editor_init.html
but i have installed django-markdown already. I would like to show the code below:
models.py:
class Entry(models.Model):
title = models.CharField(max_length=200)
body = MarkdownField()
slug = models.SlugField(max_length=200, unique=True)
publish = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
tags = models.ManyToManyField(Tag)
objects = EntryQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("entry_detail", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Blog Entry"
verbose_name_plural = "Blog Entries"
ordering = ["-created"]
admin.py:
from django.contrib import admin
from . import models
from django_markdown.admin import MarkdownModelAdmin
from django_markdown.widgets import AdminMarkdownWidget
from django.db.models import TextField
class EntryAdmin(MarkdownModelAdmin):
list_display = ("title", "created")
prepopulated_fields = {"slug": ("title",)}
# Next line is a workaround for Python 2.x
formfield_overrides = {TextField: {'widget': AdminMarkdownWidget}}
admin.site.register(models.Entry, EntryAdmin)
admin.site.register(models.Tag)
qblog/urls.py:
from django.conf.urls import patterns, include, url
from django.contrib import admin
import settings
urlpatterns = patterns(
'',
url(r'^admin/', include(admin.site.urls)),
url(r'^markdown/', include("django_markdown.urls")),
url(r'^', include('blog.urls')),
)
if settings.DEBUG:
from django.conf.urls.static import static
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
You need to add the editor_init.html file in your project.
If your project root is project/ there should be a directory called templates in there. If that directory does not exist, create it (so the path would be project/templates).
Place the editor_init.html file in this directory and everything should work.
You can find more information about setting up templates here and in the django docs.
I've followed the advice I was given but the slug still does not show in the url. When I try to run it I get the following error:
Page not found (404)
Request Method: GET
Request URL: http://localhost:8080/blog/1/
Raised by: blog.views.detail
I was told if I changed the values in my views from
detail(request, blog_id):
page = "blog/detail.html"
title = "Detail"
context = {
"post": get_object_or_404(Post, pk=blog_id),
}
to this
detail(request, slug):
page = "blog/detail.html"
title = "Detail"
context = {
"post": get_object_or_404(Post, slug=slug),
}
and changed my url to look like this
url(r'^(?P<slug>[\w-]+)/$', views.detail, name='detail'),
it would work but it does not. Heres my code as it is now below.
My post model in models.py looks like this
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
slug = models.SlugField()
img = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
author = models.ForeignKey(Author)
categories = models.ManyToManyField(Category)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
class Meta:
ordering = ["-created"]
my views.py looks like this
from django.shortcuts import render, get_object_or_404
from .models import Post
def detail(request, slug):
page = "blog/detail.html"
title = "Detail"
context = {
"post": get_object_or_404(Post, slug=slug),
"title": title,
}
return render(request, page, context)
my blog/urls.py look like this
from django.conf.urls import url
app_name = 'blog'
from . import views
urlpatterns = [
# ex: /blog/
url(r'^$', views.index, name='index'),
# ex: /blog/5/
url(r'^(?P<slug>[\w-]+)/$', views.detail, name='detail'),
# ex: /blog/contact
url(r'^contact$', views.contact, name='contact'),
]
my site/urls.py looks like this
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^blog/', include('blog.urls')),
url(r'^admin/', admin.site.urls),
url(r'^$', 'blog.views.land'),
]
my admin.py looks like this
from django.contrib import admin
from .models import Post, Author, Category, Tag
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
search_fields = ['title', 'body']
list_display = ['title', 'body', 'created']
list_filter = ['created']
admin.site.register(Post, PostAdmin)
any and all help guidance advice or tutorial is invited and welcomed. Thanks
As I can see from your post: you tried url http://localhost:8080/blog/1/, but that is the old url, you need new URL which is going to be something like http://localhost:8080/blog/slug/...
You should also change get_object_or_404(Post, slug) to get_object_or_404(Post, slug=slug).
See https://docs.djangoproject.com/en/1.9/_modules/django/shortcuts/#get_object_or_404 for details, get expects keyword arguments, not positional: https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.get
the regex is this
(?P<slug>[\w-]+)
when it should be this
(?P<slug>[\w\-]+)
thanks to all that tried to help. I'm glad I didn't give up. This made my new years!!!
Check if you really have a post with slug 1. It seems to me that you used /blog/1 to get the post by pk and so now it won't work as the post with slug 1 doesn't exist at all.
I think I done everything correct but I don't know why it doesn't work. When I try to reach a page I get error "Page not found (404)".
So, this all products page works 127.0.0.1:8000/products/ but when I try to visit single product page 127.0.0.1:8000/products/audi I get error that it's not found...
So, maybe you what's wrong here?
Thank you.
Page not found (404)
Request Method: GET
Request URL: http://link/products/audi
Using the URLconf defined in ecommerce.urls, Django tried these URL patterns, in this order:
^static/(?P<path>.*)$
^media/(?P<path>.*)$
^admin/doc/
^admin/
^products/ ^$ [name='products']
^products/ ^$(?P<slug>.*)/$
^contact/ [name='contact_us']
The current URL, products/audi, didn't match any of these.
Main project urls.py:
from django.conf import settings
from django.conf.urls import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {
'document_root': settings.STATIC_ROOT
}),
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
'document_root': settings.MEDIA_ROOT
}),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^products/', include('products.urls')),
url(r'^contact/', 'contact.views.contact_us', name='contact_us'),
)
Products app urls.py:
from django.conf import settings
from django.conf.urls import patterns, include, url
urlpatterns = patterns('products.views',
url(r'^$', 'all_products', name='products'),
url(r'^$(?P<slug>.*)/$', 'single_product'),
)
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, RequestContext, get_object_or_404
from .models import Product
def all_products(request):
products = Product.objects.filter(active=True)
return render_to_response('products/all.html', locals(), context_instance=RequestContext(request))
def single_product(request, slug):
product = get_object_or_404(Product, slug=slug)
return render_to_response('products/single.html', locals(), context_instance=RequestContext(request))
models.py
from django.db import models
# Create your models here.
class Product(models.Model):
title = models.CharField(max_length=220)
description = models.CharField(max_length=3000, null=True, blank=True)
price = models.DecimalField(max_digits=1000, decimal_places=2, null=True, blank=True)
slug = models.SlugField()
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.title
class Meta:
ordering = ['title',]
class ProductImage(models.Model):
product = models.ForeignKey(Product)
description = models.CharField(max_length=3000, null=True, blank=True)
image = models.ImageField(upload_to='product/images/')
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.image
Your URL pattern has an extra $ at the beginning, so it can never match anything:
url(r'^$(?P<slug>.*)/$', 'single_product')
This should be:
url(r'^(?P<slug>.*)/$', 'single_product')
This still requires a trailing slash, which is the normal pattern. With that corrected, your URL should be /products/audi/. You don't show the context in which that URL is created, but this is one example of why it's a good idea to use Django's url reversal to build URLs if at all possible. That would look something like this, in Python code (for instance, possibly in a get_absolute_url method on the model:
reverse('single_product', kwargs={'slug': someproduct.slug})
Or like this, in a template:
Django 1.5:
{% url 'single_product' someproduct.slug %}
Django 1.4 and earlier:
{% url single_product someproduct.slug %}
You have a typo:
url(r'^$(?P<slug>.*)/$', 'single_product'),
Note this $ just after ^, in regexp it states for a string end. Replace with
url(r'^(?P<slug>.*)/$', 'single_product'),