How do you create object with modelviewset and POST request? - python

Im trying to add a comment to my db, but getting error 'OrderedDict' object has no attribute 'pk'
The part of react.js code handling the POST request:
addComment() {
let url = this.props.post_url
axios.post('/api/comments/', {
post: url,
user: "http://127.0.0.1:8000/api/users/1/?format=json",
text: document.getElementsByName(url)[0].value,
csrfmiddlewaretoken: document.getElementsByName("csrfmiddlewaretoken")[0].value},
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
My serializers.py:
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Post, Comment
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'url')
class CommentSerializer(serializers.HyperlinkedModelSerializer):
#user = UserSerializer(many=False, required=False)
class Meta:
model = Comment
fields = ('id', "post", "user", 'text')
read_only_fields = ('id', "user")
def create(self):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
class PostSerializer(serializers.HyperlinkedModelSerializer):
#user = UserSerializer(required=False)
comments = CommentSerializer(many=True, required=False, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', "user", "url", "comments", 'text')
read_only_fields = ('id', "url", "comments")
def save(self):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
My views.py
from django.contrib.auth.models import User
from api.serializers import UserSerializer
from rest_framework import viewsets
from .models import Comment, Post
from .serializers import CommentSerializer, PostSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
When sending the Post-request it goes througth normaly. If I remove one of the fields it returnes a 400. Now Im getting this 500 [Internal server] error.
AttributeError: 'OrderedDict' object has no attribute 'pk'
The error seems to be comming from:
/home/halvor1606/.virtualenvs/django-react/local/lib/python2.7/site-packages/rest_framework/relations.py in get_url
# Unsaved objects will not yet have a valid URL.
if hasattr(obj, 'pk') and obj.pk in (None, ''):
return None
Here-> lookup_value = getattr(obj, self.lookup_field) ...
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
def get_name(self, obj):
return six.text_type(obj)
▶ Local vars are as follows:-
Variable Value
request <rest_framework.request.Request object at 0x7f4e59e75b90>
view_name 'post-detail'
obj OrderedDict([(u'title', u'adskjfj|'), (u'user', <User: halvor1606>), (u'text', u'kjkldsjf')])
self HyperlinkedIdentityField(read_only=True, view_name='post-detail')
format None
Read the other Questions with the same error. Didn't find one that solved my problem.
Thank you!
Edit:
Solved it by adding this to my post serializer:
def create(self, validated_data):
tmp_post = validated_data
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
post = Post.objects.create(
user=user,
title=tmp_post['title'],
text=tmp_post['text'],
)
return post

I think this is simply because your url here is empty:
addComment() {
let url = this.props.post_url
axios.post('/api/comments/', {
post: url,
user: "http://127.0.0.1:8000/api/users/1/?format=json",
text: document.getElementsByName(url)[0].value,
csrfmiddlewaretoken: document.getElementsByName("csrfmiddlewaretoken")[0].value},
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
Since you specified class CommentSerializer(serializers.HyperlinkedModelSerializer):, this was mentioned in the document:
There needs to be a way of determining which views should be used for
hyperlinking to model instances.
By default hyperlinks are expected to correspond to a view name that
matches the style '{model_name}-detail', and looks up the instance by
a pk keyword argument.
So the HyperlinkedModelSerializer tries to find a view which should be used for the linking to Post object and could not find it. Highly suspected that the post url is with empty id

Related

non_field_errors "No data provided" error on PUT request

I am new in Django and python. Now I am trying to do web API with Django and python. My GET, POST, and DELETE requests are working, but PUT gives me error:
{
"non_field_errors": [
"No data provided"
] }
(i used Postman)
Here's my code:
Serializer:
from rest_framework import serializers
from .models import Topic
class TopicSerializer(serializers.ModelSerializer):
title = serializers.CharField(max_length=50)
text = serializers.CharField(max_length=500)
class Meta:
model = Topic
fields = [
'title', 'text'
]
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('text', instance.description)
instance.save()
return instance
Views:
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Topic
from .serializers import TopicSerializer
class TopicView(APIView):
def get(self, request):
topics = Topic.objects.all()
serializer = TopicSerializer(topics, many=True)
return Response({'topic': serializer.data})
def post(self, request):
topic = request.data
serializer = TopicSerializer(data=topic)
if serializer.is_valid(raise_exception=True):
topic_saved = serializer.save()
return Response({'success': 'Topic {} created successfully'.format(topic_saved.title)})
def put(self, request, pk):
# saved_topic = get_object_or_404(Topic.objects.all())
saved_topic = get_object_or_404(Topic.objects.filter(id=pk))
data = request.data.get('topic')
serializer = TopicSerializer(instance=saved_topic, data=data, partial=True)
if serializer.is_valid(raise_exception=True):
topic_saved = serializer.save()
return Response({
"success": "Topic '{}' updated successfully".format(topic_saved.title)
})
def delete(self, request, pk):
topic = get_object_or_404(Topic.objects.all(), pk=pk)
topic.delete()
return Response({
"message": "Topic with id `{}` has been deleted.".format(pk)
}, status=204)
App URLs:
from django.urls import path
from .views import TopicView
app_name = "rest_test_app"
# app_name will help us do a reverse look-up latter.
urlpatterns = [
path('topics/', TopicView.as_view()),
path('topics/<int:pk>', TopicView.as_view())
]
request body:
{
"title": "pipiska",
"text": "pipiska111"
}
is this because of using wrong methods?
(sorry for terrible english)
Two changes and PUT request will work fine.
One is in the serializers.py file.
Instead of instance.description = validated_data.get('text', instance.description) change it to instance.text = validated_data.get('text', instance.text)
Another one, as mentioned in the comments, in views.py in put definition use data = request.data instead of data = request.data.get('topic')
Then give PUT request from Postman with the following request body:
{
"title": "pipiska",
"text": "pipiska111"
}
it will work fine.

Django/DRF backend not receiving complete data from POST request

I'm trying to send data to my Django/DRF backend via Axios POST requests. Some of them have been working, but some of them haven't been. This snippet, for example, works fine:
axiosInstance.post(
`/api/notes/`,
{
title,
content: serialize(editorContent),
}
)
This one, however, has been causing problems:
axiosInstance.post(
`/api/notebooks/`,
{
name: newNotebookName,
user,
}
)
In the second snippet, the name attribute gets through to the backend, but for some reason the user attribute does not. I tried using console.log to see if user is undefined at the moment it's sent off to the backend and it isn't.
I tried to change the way I define the keys in the object I send to the backend so that they're not dynamic like in this answer and the result hasn't changed.
The user variable is a string containing an email address, like 'foobar#gmail.com'. I thought maybe Django blocked it by default for security reasons, but I split the string at the # and tried sending the first part only and it didn't work either.
I also tried defining a new variable so that the type of user is string rather than string | undefined and it still doesn't work. The value of title is also string and it gets through to the backend (it's defined in the validated_data object my serializer sees in its create() method). So I'm at a loss as to what this could be. Here are some of my files:
axiosAPI.ts
import axios from 'axios'
const baseURL = 'http://localhost:8000/'
export const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
'xsrfHeaderName': 'X-CSRFTOKEN',
'xrsfCookieName': 'csrftoken',
'Authorization': localStorage.getItem('access_token') ? 'JWT ' + localStorage.getItem('access_token') : null,
'Content-Type': 'application/json',
'accept': 'application/json',
}
})
axiosInstance.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config
// Prevent infinite loops if login credentials invalid
if (error.response.status === 406 && originalRequest.url === baseURL + 'auth/token/obtain') {
window.location.href = '/login/'
return Promise.reject(error);
}
// Respond to expired refresh tokens
if (error.response.data.code === 'token_not_valid' && error.response.status === 401 && error.response.statusText === 'Unauthorized') {
const refreshToken = localStorage.getItem('refresh_token')
if (refreshToken) {
const tokenObj = JSON.parse(atob(refreshToken.split('.')[1]))
const currentTime = Math.ceil(Date.now() / 1000)
if (tokenObj.exp > currentTime) {
return axiosInstance
.post('/auth/token/refresh/', { refresh: refreshToken })
.then(response => {
localStorage.setItem('access_token', response.data.access)
localStorage.setItem('refresh_token', response.data.refresh)
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access
originalRequest.headers['Authorization'] = 'JWT ' + response.data.access
return axiosInstance(originalRequest);
})
.catch(err => {
console.log(err)
throw err;
})
} else {
console.log('Refresh token is expired.')
window.location.href = '/login/'
}
} else {
console.log('Refresh token not available.')
window.location.href = '/login/'
}
}
// Respond to invalid access tokens
if (error.response.status === 401 && error.response.statusText === 'Unauthorized') {
const refreshToken = localStorage.getItem('refresh_token')
return axiosInstance
.post('/auth/token/refresh/', { refresh: refreshToken })
.then(response => {
localStorage.setItem('access_token', response.data.access)
localStorage.setItem('refresh_token', response.data.refresh)
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access
originalRequest.headers['Authorization'] = 'JWT ' + response.data.access
return axiosInstance(originalRequest)
}).catch(err => {
console.log(err)
throw err;
})
}
}
)
notebooks/serializers.py
import bleach
import json
from rest_framework import serializers
from authentication.models import ErsatzNoteUser
from .models import Note, Notebook
class NoteSerializer(serializers.ModelSerializer):
note_id = serializers.SlugField(source='id', read_only=True, required=False)
title = serializers.JSONField(required=False)
content = serializers.CharField(required=False)
notebook = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
date_modified = serializers.DateField(read_only=True, required=False)
date_created = serializers.DateField(read_only=True, required=False)
def create(self, validated_data):
title = json.dumps(validated_data['title'])
# Workaround to fix a currently unpatched bug in Slate
# that occurs when an editor's contents begin with a list
content = validated_data['content']
if content.startswith('<ul') or content.startswith('<ol'):
content = '<p></p>' + content
response_data = {
'title': title,
'content': content,
}
return Note.objects.create(**response_data)
def update(self, instance, validated_data):
instance.title = json.dumps(validated_data['title'])
# See the above comment in the 'create' method
content = validated_data['content']
if content.startswith('<ul') or content.startswith('<ol'):
content = '<p></p>' + content
instance.content = content
instance.save()
return instance
class Meta:
model = Note
fields = [ 'note_id', 'title', 'content', 'notebook', 'date_modified', 'date_created' ]
class NotebookSerializer(serializers.ModelSerializer):
notebook_id = serializers.SlugField(source='id', read_only=True, required=False)
name = serializers.CharField(max_length=64, default='')
notes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
def create(self, validated_data):
# notebook_data = {
# 'name': validated_data['name'],
# 'user': ErsatzNoteUser.objects.get(email=validated_data['user']),
# }
print(validated_data)
return Notebook.objects.create(name=validated_data['name'], user=ErsatzNoteUser.objects.get(email=validated_data['user']))
class Meta:
model = Notebook
fields = [ 'notebook_id', 'name', 'notes', 'date_modified', 'date_created' ]
notebooks/views.py
import json
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .serializers import NoteSerializer, NotebookSerializer
from .models import Note, Notebook
class NoteViewSet(viewsets.ModelViewSet):
serializer_class = NoteSerializer
def get_queryset(self):
return self.request.user.notes.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = NoteSerializer(instance)
title = json.loads(serializer.data['title'])
response_data = {
'note_id': serializer.data['note_id'],
'title': title,
'content': serializer.data['content'],
'notebook': serializer.data['notebook'],
'date_modified': serializer.data['date_modified'],
'date_created': serializer.data['date_created'],
}
return Response(response_data)
class NotebookViewSet(viewsets.ModelViewSet):
serializer_class = NotebookSerializer
def get_queryset(self):
return self.request.user.notebooks.all()
notebooks/models.py
from django.db import models
from django.contrib.auth.models import User
from jsonfield import JSONField
from django.conf import settings
from .helpers import generate_slug
class Note(models.Model):
""" Represents an individual note. """
id = models.SlugField(max_length=settings.MAX_SLUG_LENGTH, primary_key=True)
title = JSONField(null=True)
content = models.TextField(null=True)
notebook = models.ForeignKey('Notebook', related_name='notes', on_delete=models.CASCADE, null=True, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notes', on_delete=models.CASCADE, null=True, blank=True)
date_created = models.DateField(auto_now_add=True)
date_modified = models.DateField(auto_now=True)
def save(self, *args, **kwargs):
if not self.id:
self.id = generate_slug(self, settings.MAX_SLUG_LENGTH)
# Temporary expedients for the sake of development
if not self.notebook:
self.notebook = Notebook.objects.get(id='YyOzNhMFMPtN8HM')
super(Note, self).save(*args, **kwargs)
def __str__(self):
return self.id
class Meta:
ordering = [ '-date_modified', '-date_created', ]
class Notebook(models.Model):
""" A collection of individual notes. """
id = models.SlugField(max_length=settings.MAX_SLUG_LENGTH, primary_key=True)
name = models.CharField(max_length=64, default='')
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notebooks', on_delete=models.CASCADE)
date_created = models.DateField(auto_now_add=True)
date_modified = models.DateField(auto_now=True)
def save(self, *args, **kwargs):
if not self.id:
self.id = generate_slug(self, settings.MAX_SLUG_LENGTH)
super(Notebook, self).save(*args, **kwargs)
def __str__(self):
return self.name
class Meta:
ordering = [ '-date_modified', '-date_created', 'name' ]
Please don't hesitate to ask if you'd like further info.
EDIT: Changed NotebookViewSet so that it has a retrieve() method, the result hasn't changed:
class NotebookViewSet(viewsets.ModelViewSet):
serializer_class = NotebookSerializer
def get_queryset(self):
return self.request.user.notebooks.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = NotebookSerializer(instance)
response_data = {
'name': serializer.data['name'],
'user': serializer.data['user'],
}
return Response(response_data)
In your example your NotebookViewSet class does not have a retrieve method.
It turns out that under the hood, DRF considers ForeignKey fields to be read_only. Meaning it's solely to be used for representing the object, and shouldn't be part of any create or update process. According to the documentation:
Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored.
Source.
But this didn't matter to my database, which requires me to identify the user that corresponds to each new Notebook at the time of its creation, or else a NOT NULL constraint is violated. So I just overwrote the default ModelViewSet create() method to pass on the user string manually to my NotebookSerializer as part of context, allowing me to use it to get the corresponding user object in the database and assign it to the newly created Notebook. I went about it like this:
class NotebookViewSet(viewsets.ModelViewSet):
serializer_class = NotebookSerializer
def get_queryset(self):
return self.request.user.notebooks.all()
def perform_create(self, serializer):
serializer.save()
def create(self, request, *args, **kwargs):
serializer = NotebookSerializer(data=request.data, context={ 'request': request })
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class NotebookSerializer(serializers.ModelSerializer):
notebook_id = serializers.SlugField(source='id', read_only=True, required=False)
name = serializers.CharField(max_length=64, default='')
notes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
def create(self, validated_data):
return Notebook.objects.create(
user=ErsatzNoteUser.objects.get(email=self.context['request'].user),
**validated_data
)
class Meta:
model = Notebook
fields = [ 'notebook_id', 'name', 'notes', 'date_modified', 'date_created' ]
That's all it took.

Why is not `url` showing up in the DRF Response

I can't get url to be returned when I use it in a HyperlinkedModelSerializer.
# models.py
class Promotion(TimeStampedMixin, models.Model):
name = models.CharField(max_length=300)
# ...
# views.py
class PromotionViewSet(viewsets.ModelViewSet):
serializer_class = PromotionSerializer
queryset = Promotion.objects.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, context={'request': request})
return Response(serializer.data)
# serializers.py
class PromotionSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name="campaigns:promotion-detail", read_only=True
)
class Meta:
model = Promotion
fields = (
"url",
"id",
"name",
)
The JSON output I receive when querying curl -X GET http://localhost/api/promotion/2/:
{"id":2,"name":"My promotion"}
If I use reverse to check if the view_name in the HyperlinkedRelatedField exists, it prints the correct URL.
My question is: why doesn't the url show up in the response? It works any all my other views (comparing code with classes that works hasn't helped). Read the DRF documentation but I see nothing new (using version 3.11.0)
To get the url representation to the current object you should use the HyperlinkedIdentityField
class PromotionSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="campaigns:promotion-detail", read_only=True
)
class Meta:
model = Promotion
fields = (
"url",
"id",
"name",
)

AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context

I want to create a many-to-many relationship where one person can be in many clubs and one club can have many persons. I added the models.py and serializers.py for the following logic but when I try to serialize it in the command prompt, I get the following error - What am I doing wrong here? I don't even have a HyperlinkedIdentityField
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 503, in data
ret = super(Serializer, self).data
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 239, in data
self._data = self.to_representation(self.instance)
File "C:\Users\user\corr\lib\site-packages\rest_framework\serializers.py", line 472, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "C:\Users\user\corr\lib\site-packages\rest_framework\relations.py", line 320, in to_representation"the serializer." % self.__class__.__name__
AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
models.py
class Club(models.Model):
club_name = models.CharField(default='',blank=False,max_length=100)
class Person(models.Model):
person_name = models.CharField(default='',blank=False,max_length=200)
clubs = models.ManyToManyField(Club)
serializers.py
class ClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club
fields = ('url','id','club_name','person')
class PersonSerializer(serializers.ModelSerializer):
clubs = ClubSerializer()
class Meta:
model = Person
fields = ('url','id','person_name','clubs')
views.py
class ClubDetail(generics.ListCreateAPIView):
serializer_class = ClubSerializer
def get_queryset(self):
club = Clubs.objects.get(pk=self.kwargs.get('pk',None))
persons = Person.objects.filter(club=club)
return persons
class ClubList(generics.ListCreateAPIView):
queryset = Club.objects.all()
serializer_class = ClubSerializer
class PersonDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PersonSerializer
def get_object(self):
person_id = self.kwargs.get('pk',None)
return Person.objects.get(pk=person_id)
Inspecting the created serializer gives me this -
PersonSerializer(<Person: fd>):
url = HyperlinkedIdentityField(view_name='person-detail')
id = IntegerField(label='ID', read_only=True)
person_name = CharField(max_length=200, required=False)
clubs = ClubSerializer():
url = HyperlinkedIdentityField(view_name='club-detail')
id = IntegerField(label='ID', read_only=True)
club_name = CharField(max_length=100, required=False)
but serializer.data gives me the error
Edit
I realized the error could be because of url patterns, so I added the following url patterns but I still get the error -
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^clubs/$',
views.ClubList.as_view(),
name='club-list'),
url(r'^clubs/(?P<pk>[0-9]+)/persons/$',
views.ClubDetail.as_view(),
name='club-detail'),
url(r'^person/(?P<pk>[0-9]+)/$',
views.PersonDetail.as_view(),
name='person-detail'),
])
You're getting this error as the HyperlinkedIdentityField expects to receive request in context of the serializer so it can build absolute URLs. As you are initializing your serializer on the command line, you don't have access to request and so receive an error.
If you need to check your serializer on the command line, you'd need to do something like this:
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory
from .models import Person
from .serializers import PersonSerializer
factory = APIRequestFactory()
request = factory.get('/')
serializer_context = {
'request': Request(request),
}
p = Person.objects.first()
s = PersonSerializer(instance=p, context=serializer_context)
print s.data
Your url field would look something like http://testserver/person/1/.
I have two solutions...
urls.py
1)
If you are using a router.register, you can add the base_name:
router.register(r'users', views.UserViewSet, base_name='users')
urlpatterns = [
url(r'', include(router.urls)),
]
2)
If you have something like this:
urlpatterns = [
url(r'^user/$', views.UserRequestViewSet.as_view()),
]
You have to pass the context to the serializer:
views.py
class UserRequestViewSet(APIView):
def get(self, request, pk=None, format=None):
user = ...
serializer_context = {
'request': request,
}
serializer = api_serializers.UserSerializer(user, context=serializer_context)
return Response(serializer.data)
Like this you can continue to use the url on your serializer:
serializers.py
...
url = serializers.HyperlinkedIdentityField(view_name="user")
...
I came across the same problem. My approach is to remove 'url' from Meta.fields in serializer.py.
Following Slipstream's answer, I edited my views.py introducing the context and now it works.
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().select_related('profile').order_by('-date_joined')
serializer_class = UserSerializer
#list_route(methods=['get'], url_path='username/(?P<username>\w+)')
def getByUsername(self, request, username):
serializer_context = {
'request': request,
}
user = get_object_or_404(User, username=username)
return Response(UserSerializer(user, context=serializer_context).data, status=status.HTTP_200_OK)
You can simply pass None to 'request' key in context in situations where you just need the relative URL, e.g; testing a serializer in command line.
serializer = YourModelSerializer(modelInstance_or_obj, context={'request': None})
Following MDT's response, I use django-rest-framework, and solve it by changing request to request._request.
serializer_context = {'request': Request(request._request)}
You may simply solve it by changing the instantiation (in views.py) to thing like this:
your_serializer = YourModelSerializer(YourQuerySet_or_object, many=True,context={'request':request})
For externals urls you can simply put request at None:
context={
'request': None
},
In my case I had to change a field's name from url to any other thing. Hate automagic

How to create a model with foreign keys with SwampDragon?

I'm creating a chat demo using SwampDragon and I'm unsure how to create a model with related fields.
Using the code below, an error occurs each time I call the createMessage function in javascript. The only error I receive is from the websocket output: c[3001,"Connection aborted"]. There is no output on the python console to shed any extra light on this error.
Can anyone help me understand how to create a Message, with the related field of User?
Model:
from django.contrib.auth.models import User
from django.db import models
from swampdragon.models import SelfPublishModel
from .serializers import MessageSerializer
class Message(SelfPublishModel, models.Model):
serializer_class = MessageSerializer
user = models.ForeignKey(User, related_name='messages')
body = models.TextField(blank=True)
Serializer:
from swampdragon.serializers.model_serializer import ModelSerializer
from django.contrib.auth.models import User
class UserSerializer(ModelSerializer):
class Meta:
model = User
publish_fields = ('id', 'username')
update_fields = ('first_name', 'last_name', )
class MessageSerializer(ModelSerializer):
user = UserSerializer
class Meta:
model = b'chat.Message'
publish_fields = ('body', 'user', )
update_fields = ('body', 'user', )
Route:
from swampdragon import route_handler
from swampdragon.route_handler import ModelRouter, BaseRouter
from .models import Message
from .serializers import MessageSerializer, UserSerializer
class MessageRouter(ModelRouter):
route_name = 'message'
serializer_class = MessageSerializer
model = Message
include_related = [UserSerializer, ]
def get_object(self, **kwargs):
return self.model.objects.get(pk=kwargs['id'])
def get_query_set(self, **kwargs):
return self.model.objects.all()
route_handler.register(MessageRouter)
Javascipt:
...
createMessage: function(message){
var data = {
user: 1,
body: message
};
swampdragon.create('message', data, function (context, data) {
console.log('MessageStore: onCreate success.', context, data);
}, function (context, data) {
console.log('MessageStore: onCreate failed.', context, data);
});
},
...
Requirements
django==1.7.6
SwampDragon==0.4.1.2
SwampDragon-auth==0.1.3
To answer my own question: This is not built into the framework as I expected. You need to instantiate the related models in the router in the get_initial method. These will then be passed to the serializer and will create the Message model correctly.
Route
class MessageRouter(ModelRouter):
route_name = 'message'
serializer_class = MessageSerializer
model = Message
include_related = [UserSerializer, ]
def get_initial(self, verb, **kwargs):
user = User.objects.get(pk=kwargs['user_id'])
return {'user': user}
def get_object(self, **kwargs):
return self.model.objects.get(pk=kwargs['id'])
def get_query_set(self, **kwargs):
return self.model.objects.all()
Javascript
...
createMessage: function(message){
var data = {
user_id: 1,
body: message
};
swampdragon.create('message', data, function (context, data) {
console.log('MessageStore: onCreate success.', context, data);
}, function (context, data) {
console.log('MessageStore: onCreate failed.', context, data);
});
},
...
Serializer
class MessageSerializer(ModelSerializer):
user = UserSerializer
class Meta:
model = b'chat.Message'
publish_fields = ('body', 'user', )
update_fields = ('body', )

Categories