I'm currently starting a simple Task App and I'm using Django 2.0.7, DRF 3.8.2 and drf-nested-routes 0.90.2
I have these models :
class Client(TimeStampedModel):
"""
This model describes a client for the railroader. It can be created by the manager in the back office
We have at least one internal Client, which is Seelk, for internal projects
"""
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return "{} : {}".format(self.name, self.description)
class Project(TimeStampedModel):
"""
This model represents a project for a client, which we are gonna track actions on
"""
client = models.ForeignKey(
'railroader.Client', on_delete=models.PROTECT, related_name='projects')
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return "{} for client {}".format(self.name, self.client.name)
So, following the documentation of drf-nested-routers, I set up my serializers like this :
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ("id", "name", "description", "is_active", "projects")
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "name", "description", "is_active")
And my viewsets like this :
class ClientViewset(viewsets.ModelViewSet):
serializer_class = ClientSerializer
permission_classes = (permissions.IsAuthenticated, AccountPermission)
def get_queryset(self):
queryset = Client.objects.all()
is_active = self.request.query_params.get("is_active")
if is_active:
queryset = queryset.filter(is_active=is_active)
return queryset
class ProjectViewset(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = (permissions.IsAuthenticated, AccountPermission)
def get_queryset(self):
queryset = Project.objects.filter(client=self.kwargs["client_pk"])
is_active = self.request.query_params.get("is_active")
if is_active:
queryset = queryset.filter(is_active=is_active)
return queryset
And finally, my urls like so :
router = routers.SimpleRouter()
router.register(r"clients", viewsets.ClientViewset, base_name="clients")
projects_router = routers.NestedSimpleRouter(router, r"clients", lookup="client")
projects_router.register(r"projects", viewsets.ProjectViewset, base_name="projects")
urlpatterns = [
re_path(r"^", include(router.urls)),
re_path(r"^", include(projects_router.urls))
]
With this setup, I'm able to have the desired nested routes, but I can't have my routes to create a new object if I post on a nested route.
I've seen an issue on the github speaking about it, but as it was 2 years ago, I wonder if anyone knows how to do it.
Thanks in advance.
Found out I just forgot that returning an instance in DRF create method of the serializer would not create the object in base. At the end I have this serializer :
class ProjectSerializer(serializers.ModelSerializer):
def create(self, validated_data):
client = Client.objects.get(pk=self.context["view"].kwargs["client_pk"])
validated_data["client"] = client
return Project.objects.create(**validated_data)
class Meta:
model = Project
fields = ("id", "name", "description", "is_active")
Related
I am not very professional in django rest...
I wrote a blog with django rest framework and There is no problem when I want to get all the records related to the Article model or get a specific article, for example
But what I want to do is to send an user id(or an user name) to the view when I click on the user's name.
and as a result display all the records of the Article model related to the request.user and all the records of the Article model related to the user whose name was clicked.
In fact, I want to click on the name of each user, in addition to getting the Articles of that user, the Articles related to the request.user will also be taken
This is what I have done so far...
#models.py
class Article(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField
author = models.ForeignKey(User , on_delete = models.CASCADE)
content = models.TextField(null = True)
publish = models.DateTimeField(default = timezone.now)
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
status = models.BooleanField(default = False)
def __str__(self):
return self.title
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
pic = models.ImageField(upload_to="img", blank=True, null=True)
def __str__(self):
return self.user.username
#views.py
class ArticleCreate(CreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleList(ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleDetail(RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class UserDetail(RetrieveUpdateDestroyAPIView):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
class UserProfile(RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
#serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = "__all__"
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
exclude = ['updated' , 'created']
You should directly make several modifications in get_queryset() method by using Q objects so:
class ArticleList(ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_queryset(self):
user_id = self.kwargs.get('user_id')
if user_id:
articles = Article.objects.filter(Q(author_id=user_id) | Q(author=self.request.user))
return articles
return self.queryset
You'll also need to modify your urls.py file to include the user_id parameter in the URL so:
from django.urls import path
from .views import ArticleList
urlpatterns = [
path('articles/<int:user_id>/', ArticleList.as_view(), name='article_list'),
# ... Other routes.
]
example URL: http://example.com/api/purchases?username=denvercoder9
class ArticleList(ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_queryset(self):
username = self.request.query_params.get('username')
if username:
return User.objects.filter(username=username).article_set.all()
user = self.request.user
return Article.objects.filter(author=user)
I would like to create my own endpoint for POST request to two related tables. I have two tables User and Userattribute.
models.py
class User(models.Model):
email = models.CharField(unique=True, max_length=180)
roles = models.JSONField(default=dict)
password = models.CharField(max_length=255, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
firebase_id = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(default=now)
progress_sub_step = models.IntegerField(blank=True, null=True)
step_available_date = models.DateTimeField(blank=True, null=True)
progress_step = models.IntegerField(blank=True, null=True)
active = models.IntegerField(default=1)
last_login_at = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'user'
class Userattribute(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name = 'attribute')
attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
The table Userattribute contains the field user which is OnetoOne to Id primary key from User table.
I tried to implement POST to two tables in serializers.py In the commented section there is a create definition which works perfectly for me. However, I wouldlike to move it to views.py as register_in_course endpoint
serializers.py
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ['tytul', 'opis', 'po_premierze']
class UserattributeSerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['user', 'attribute']
class UASerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['attribute']
class UserSerializer(serializers.ModelSerializer):
attribute = UASerializer(many = False)
class Meta:
model = User
fields = ['email', 'name', 'firebase_id', 'attribute']
# This is what workks perfectly for me, and I want to move it to views.py
# VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
# def create(self, validated_data):
# attribute_data = validated_data.pop('attribute')
# user = User.objects.create(**validated_data)
# Userattribute.objects.create(user=user, **attribute_data)
# return user
Current views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
user_id = User.objects.filter(firebase_id = request.data['firebase_id'])['id']
attribute = Userattribute.objects.create(user = user_id, attribute = request.data['attribute']['attribute'])
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
Using endpoint register_in_course to POST I get following error:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.
urls.py
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'userattribute', views.UserattributeViewSet)
urlpatterns = [
url('', include(router.urls))
]
i removed one line user_id variable and changed attribute variable. please check, maybe it should solve your problem, because you have already have Assigned variable as a User object..
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
attribute = Userattribute.objects.create(user = user, attribute = request.data['attribute']['attribute']) # changed this line
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
This issue is caused by calling get_object in a view that is defined with detail=False:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object() # The problem is caused by this line
It seems you don't need this data, as you are using request.data.
So you can define your view like this:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
user = User.objects.create(
email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id']
)
Userattribute.objects.create(
user=user,
attribute = request.data.get('attribute', {}).get('attribute', {})
)
return Response(UserSerializer(user).data)
I want a viewset that handles a post request that creates some nested objects using the post data.
I have these models, serializers, and views:
Models:
class Connection(models.Model):
from portfolio.models import Portfolio
user = models.ForeignKey(User, related_name='exchange_connections', on_delete=models.CASCADE)
portfolios = models.ManyToManyField(Portfolio)
class ConnectionSettings(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='settings', on_delete=models.CASCADE)
import_past_transactions = models.BooleanField(default=False, blank=True)
class ConnectionCredentials(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='credentials', on_delete=models.CASCADE)
key = models.CharField(max_length=300, blank=False, null=False)
secret = models.CharField(max_length=300, blank=False, null=False)
passphrase = models.CharField(max_length=300, blank=True, null=True)
Serializers:
class ConnectionCredentialsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionCredentials
fields = '__all__'
class ConnectionSettingsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionSettings
fields = '__all__'
class ConnectionSerializer(serializers.ModelSerializer):
credentials = ConnectionCredentialsSerializer()
settings = ConnectionSettingsSerializer()
class Meta:
model = Connection
fields = '__all__'
def create(self, validated_data):
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Views:
class ConnectionViewSet(viewsets.ViewSet):
serializer_class = serializers.ConnectionSerializer
queryset = exchange_models.Connection.objects.all()
permission_classes = (IsAuthenticated, core_permissions.IsMineOnly)
def list(self):
return HttpResponse(self.request.user.exchange_connections_set)
def retrieve(self, request, pk=None):
serialized_data = self.serializer_class(exchange_models.Connection.objects.get(id=pk)).data
return HttpResponse(serialized_data)
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.create(serializer.data)
serializer.save()
return Response({'status': 'connection created.'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
URLs:
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'connections', views.ConnectionViewSet)
urlpatterns = [
path('', include(router.urls)),
]
POST Request:
When I send the post request, the portfolios and the user already exist. So I should only give primary keys to these rows in my request.
But I need to create new rows for Settings and Credentials for which I should pass data in the request.
By default nested serializers are read-only. You will have to customize the create method to make it writable.
class ConnectionSerializer(serializers.ModelSerializer):
portfolios = PortfolioSerializer(many=True)
credentials = ConnectionCredentialsSerializer(many=False)
settings = ConnectionSettings(many=False)
class Meta:
model = models.Connection
exclude = ('user',)
read_only_fields = ('date_created', 'last_updated')
def create(self, validated_data):
portfolios = validated_data.pop('portfolios')
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
for portfolio in portfolios:
Portfolio.objects.create(exchange_connection=connection, **portfolio)
for credential in credentials:
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
for setting in settings:
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Provided that following are the model names relative to serializers.
PortfolioSerializer -> Portfolio,
ConnectionCredentialsSerializer -> ConnectionCredentials
I'm starting to work with Django and I followed the tutorial available in the Django Rest Framework website and now I'm trying to adapt the tutorial to do something a little more complex.
I'm trying to create a "Like" system for a Social Network. A User can create Posts (UserPosts) and Like other user's posts.
I'm creating new UserPosts (using the command line) this way:
http -a admin:Pass1234 POST http://127.0.0.1:8000/posts/ description="I'm just a random comment"
And everything works just fine.
The problem is when I try to create a Like instance. In this case I need to pass a UserPost id, so I'm doing the same as I did to create a new comment:
http -a admin:Pass1234 POST http://127.0.0.1:8000/likes/ post="1"
But when I do this I get the following error:
"post": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got unicode."
]
}
The models are the following:
class UserPost(models.Model):
owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=100, blank=True, default='')
def save(self, *args, **kwargs):
options = self.description and {'description': self.description} or {}
super(UserPost, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
class Like(models.Model):
owner = models.ForeignKey('auth.User', related_name='likes', on_delete=models.CASCADE)
post = models.ForeignKey(UserPost, related_name='likes', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
options = self.post and {'post': self.post}
super(Like, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
The serializers:
class UserPostSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = UserPost
fields = ('url', 'id', 'description', 'owner', 'timestamp')
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
post = UserPostSerializer(source='post')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
The Views:
class PostViewSet(viewsets.ModelViewSet):
queryset = UserPost.objects.all()
serializer_class = UserPostSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class LikeViewSet(viewsets.ModelViewSet):
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Routers and urls:
router = DefaultRouter()
router.register(r'posts', views.PostViewSet)
router.register(r'users', views.UserViewSet)
router.register(r'likes', views.LikeViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Any idea of what is happening?
Thanks!
You need to remove post = UserPostSerializer from your LikeSerializer:
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
so that django-rest-framework uses a PrimaryKeyRelatedField for the related UserPost (which is the default for related models with ModelSerializer).
Then you can create your Like entry with post="1" as parameter.
I have an endpoint in my Django-rest application in which I expect to receive the following get response:
{
"my_objects": [
{
"my_object_order": 1,
"related_topics": [{"title": "my_title", "subtitle": "my_subtitle"}, {"title": "my_title2", "subtitle": "my_subtitle2"}],
"collected_at": "2016-05-02T20:52:38.989Z",
}]
}
In order to achieve that, below you can observe my serializers.py
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = MyTopic
fields = ["title", "subtitle"]
class MyObjectSerializer(serializers.ModelSerializer):
related_topics = TopicSerializer(many=True)
class Meta:
model = MyObject
fields = ("my_object_order",
"related_topics")
def create(self, validated_data):
"""
Saving serialized data
"""
related_topics_list = validated_data.pop("related_topics", [])
obj = MyObject.objects.create(**validated_data)
for topics_data in related_topics_list:
MyTopic.objects.create(trend=trend, **topics_data)
return obj
As suggested, here you can see my models.py
class MyObject(models.Model):
my_object_order = models.IntegerField()
collected_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.story_title
class MyTopic(models.Model):
my_obj = models.ForeignKey(MyObject, related_name="related_topics")
title = models.CharField(max_length=50, blank=False, null=True)
subtitle = models.CharField(max_length=50, blank=True, null=True)
def __unicode__(self):
return self.title
Below you have the excerpt from my views.py
def get(self, request):
params = request.QUERY_PARAMS
# Filtering data
obj_list = my_fun(MyObject, params)
response = {"my_objects": obj_list.values("my_object_order",
"collected_at",
"related_topics")}
return Response(response)
I have looked on the documentation, however I am confused/not understanding fundamentally what I should do.
Your problem is in views.py, you are not using actually the serializer at all. You are just filter some data and return whatever values you get from database (hence the ids only).
I suggest you to check Generic Class Based Views
from myapp.models import MyObject
from myapp.serializers import MyObjectSerializer
from rest_framework import generics
class MyObjectListAPIView(generics.ListAPIView):
queryset = MyObject.objects.all()
serializer_class = MyObjectSerializer
Also if you need any filtering check documentation here. Basically you can filter by fields from model with this snippet
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('field1', 'field2')
PS: You can do the view as normal function, but you have to handle yourself filtering/serialization part, the code may not look as cleaner as you get with class based views.