Here is my setup:
settings.py
SHARED_APPS = (
'django_tenants',
'main',
other apps...
)
TENANT_APPS = (
'rest_framework',
'company',
)
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
other middleware...
]
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
urls.py
from django.urls import include, path
from rest_framework import routers
# other imports
from main.api.v1 import projects
router = routers.DefaultRouter()
router.register(r'api/v1/project', projects.ProjectViewSet)
urlpatterns = [
-- other paths --
path('', include(router.urls)),
]
api/v1/project.py
# other imports
from company.models import Project
from rest_framework import serializers
from rest_framework import viewsets
from rest_framework import permissions
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Project
fields = ['url', 'name', 'keycode']
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all().order_by('id')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
main.models.py
from django.contrib.auth.models import User as AuthUser
from django_tenants.models import TenantMixin, DomainMixin
# note, definition of custom "User" model which has an AuthUser 1 to 1 relationship
class Company(TenantMixin):
name = models.CharField(max_length=100)
subdomain = models.CharField(max_length=32)
employees = models.ManyToManyField(User, related_name='companies')
migration_id = models.IntegerField(null=True)
class Domain(DomainMixin):
pass
company.models.py
from django.db import models
class Project(models.Model):
name = models.CharField(max_length=100)
keycode = models.CharField(max_length=8)
And the final detail is that I am not using a Django frontend but rather one created in React. The request which is going to the backend is just a standard request however it is coming from a subdomain and includes a JWT token (but I don't know if that is relevant), here is a shortened version of the request headers:
Request URL: http://localhost:8000/api/v1/project/
Request Method: GET
Authorization: Bearer <token here>
Origin: http://cbd.localhost:3000
The error that I am seeing on the backend is this:
relation "company_project" does not exist
My guess is that this is happening due to the fact that when this query is created in the ProjectViewSet:
queryset = Project.objects.all().order_by('id')
The request is not done within the context of a tenant. But my question is how exactly would that be done. I see in Django Tenants that there is a construct that looks like this:
with tenant_context(tenant):
# All commands here are ran under the schema from the `tenant` object
But I have no idea how I would go about getting the 'tenant' parameter in the class definition where it seems to be needed.
Any ideas?
Well, I figured out one way to do it, but it's ugly.
in my settings.py file I added a new constant:
BASE_TENANT = "cbd_co"
I set this to my first tenant (in my system there will be a default tenant schema which is not actually owned by a client but is a sort of template)
Then, in my model viewset declaration i did this:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.raw('SELECT * FROM "' + settings.BASE_TENANT + '"."company_project";')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
schema = self.request.META['HTTP_ORIGIN'].split("//")[1].split('.')[0] + '_co'
qs = Project.objects.raw('SELECT * FROM "' + schema + '"."company_project" ORDER BY "company_project"."id" ASC;')
return qs
I'm still hoping someone suggests a better solution though...
Notes on this solution. As it is, it is completely insecure. What I will actually have to do is get the authorization header from the request, verify the JWT token, get the user, get all the valid user companies (which also determines the valid subdomains and therefore the valid schemas. and only then would I return the queryset.
I am new to django rest framework. I was trying to build a API where I want to edit the POST method so that I could execute some actions and change some data from the POST body.
I tried to follow some documentations and Guide from django rest framework website but didnt followed any. Please help me.
Here I need to set some values for some fields that are going to be saved in the database.
views.py
from .models import LoginActivity
from .serializers import LoginActivitySerializers
class LoginActivity(viewsets.ModelViewSet):
queryset = LoginActivity.objects.all()
serializer_class = LoginActivitySerializers
urls.py
from django.urls import path, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'LoginActivity', views.LoginActivity, basename='LoginActivity')
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
appname='api'
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
serializers.py
from rest_framework import serializers
from .models import LoginActivity
class LoginActivitySerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginActivity
fields = ('id', 'user_id', 'datetimelog', 'device_os', 'device_token', 'device_model')
Please Help me.
you can override rest framework create method which come from viewsets.ModelViewSet and update request data or perform some other actions.
from .models import LoginActivity
from .serializers import LoginActivitySerializers
class LoginActivity(viewsets.ModelViewSet):
queryset = LoginActivity.objects.all()
serializer_class = LoginActivitySerializers
def create(self, request, *args, **kwargs):
# here you have your post data in request.data
data = request.data
# you can do some action here just before create action
# after that you can call super method or return your response
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(status=status.HTTP_201_CREATED)
Am trying to familiarize myself with the Django Rest Framework. Tried an experiment to list the users I created but the serializer always returns blank objects. Here's my code:
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
class CurrentUserSerializer(serializers.Serializer):
class Meta:
model = User
fields = ('username', 'email', 'id')
urls.py
from django.urls import include, path
from administration import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'admin', views.CurrentUserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
views.py
from django.shortcuts import render
from django.contrib.auth.models import User
from administration.serializers import CurrentUserSerializer
from rest_framework import viewsets
# Create your views here.
class CurrentUserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = CurrentUserSerializer
Rest of the code is pretty much boilerplate based on the DRF tutorial.
The result I'm getting is blank objects for every user. The number of blanks grows and shrinks if I add/remove a user, which tells me I'm at least partially hooked up to the User model. Example below:
[{},{},{}]
What I'm expecting is something like:
[{"username": "jimjones", "email": "jim&jones.com", "id": 0},{...}]
Any help appreciated.
Solved. Was a typo. In serializers.py the class should be using "serializers.ModelSerializer" and not "serializers.Serializer".
I've created an APIs endpoints for mobile developers with all of the logic there. Now I need to create a web version of this app. Is it possible to not write the views specifically for this, but use already existing one in APIs?
In API I am using Django Rest Framework ViewSets.
Assume that this is my models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=20)
description = models.CharField(max_length=100, blank=True, null=True)
Then this is going to be my serializers.py:
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'description')
read_only_fields = ('id',)
viewsets.py:
from rest_framework import viewsets
from .serializers import PostSerializer
from .models import Post
class PostViewSet(viewsets.ModelViewSet):
model = Post
queryset = Post.objects.all()
serializer_class = PostSerializer
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'posts', viewsets.Post, base_name='post')
urlpatterns = router.urls
I've seen this HTML & Forms in docs, but if I put renderer_classes and template_name it just renders html with the parameters that I want, but I want to have a separate endpoints for just returning json for mobile developers and rendering a user-friendly html for web version. Is it possible to do this and if it is, how do I handle this?
I think you will have to manually create a separate endpoint to handle HTML requests, but you can easily achieve this using Template Renderers
TL;DR: I am getting this error and don't know why:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field.
I am going through the django-rest-framework tutorial and am currently at a point where function based views (FBV) were switched to class, mixin, and generic based views (CBV, MBV, GBV respectively). After switching to GBV, when I went to test my API, I received this error AssertionError: Expected view SnippetDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the '.lookup_field' attribute on the view correctly.. I did some research and found that lookup_field needs to be set to the in the urlpatterns. Currently, my urls.py look like this:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<id>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<id>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
# Login and logout views for the browsable API
urlpatterns += [
url(r'^auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
and my views.py look like so:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from snippets.permissions import IsOwnerOrReadOnly
from rest_framework import generics
from rest_framework import permissions
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django.contrib.auth.models import User
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly, )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly, )
when I add lookup_field = 'id' in both UserDetail and SnippetDetail, the exception resolves itself. (Yay!). But, when I visit http://127.0.0.1/users/1/ ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field. is thrown. When I check the console, however, there is a second exception:
django.urls.exceptions.NoReverseMatch: Reverse for 'user-detail' with
arguments '()' and keyword arguments '{'pk': 1}' not found. 2
pattern(s) tried: ['users/(?P[0-9]+)\.(?P[a-z0-9]+)/?$',
'users/(?P[0-9]+)/$']
During handling of the above exception, another exception occurred:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for
hyperlinked relationship using view name "user-detail". You may have
failed to include the related model in your API, or incorrectly
configured the 'lookup_field' attribute on this field.
What I find interesting is that the kwargs for the first exception is {'pk': 1}, not {'id':1}. After some help from chat, someone pointed me to this piece of information:
Note that when using hyperlinked APIs you'll need to ensure that both the API views and the serializer classes set the lookup fields if you need to use a custom value.
This is useful as User serializer extends HyperlinkedModelSerializer:
from rest_framework import serializers
from django.contrib.auth.models import User
from snippets.models import Snippet
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
the User model and serializer has a reverse relationship with Snippet. Now, when I add lookup_field='id' to snippets attribute of UserSerializer (snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True, lookup_field='id')), like it asks me to do so here, the error is persistent.
What am I doing incorrectly? What can I do to fix this? Is it not having anything to do with lookup_id?
I understand that I could replace <id> with <pk> in my urlpatterns, but I would like to understand why this is happening.
For anyone else coming across this question after following the the tutorial, here is the answer.
Change these lines
router.register(r'snippets', views.SnippetViewSet,basename="snippets")
router.register(r'users', views.UserViewSet,basename="users")
To these (note the singular basenames)
router.register(r'snippets', views.SnippetViewSet,basename="snippet")
router.register(r'users', views.UserViewSet,basename="user")
If you follow it exactly as the tutorial exactly as written, it generates these router URLs (notice snippets-details). This is what causes the error
<URLPattern '^snippets\.(?P<format>[a-z0-9]+)/?$' [name='snippets-list']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/$' [name='snippets-detail']>
<URLPattern '^snippets/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='snippets-detail']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/highlight/$' [name='snippets-highlight']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/highlight\.(?P<format>[a-z0-9]+)/?$' [name='snippets-highlight']>
I eventually fixed my second exception by printing the serializer in the Django shell/python interactive console. The result I got was this:
>>> from snippets.serializers import UserSerializer
>>> print(UserSerializer())
UserSerializer():
url = HyperlinkedIdentityField(view_name='user-detail')
id = IntegerField(label='ID', read_only=True)
username = CharField(help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, validators=[<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>])
snippets = HyperlinkedRelatedField(lookup_field='id', many=True, read_only=True, view_name='snippet-detail')
It turns out that to change <pk> to <id> in urlspatterns, you need to add url = HyperlinkedIdentityField(view_name='user-detail', lookup_field='id') in the UserSerializer class.
I just had the same problem and I found, that the HyperlinkedIdentityField wants to insert some placeholder into your URL. But I had used URLs which did not require any placeholders to be set. A ListCreateAPIView to be precise:
url(
r'^users/$', #<-- takes no params
views.UserListView.as_view(), #<-- just prints a list
name="user-list" #<-- HyperlinkedIdentityField pointing
), # here complained bitterly.