How to save file in django rest - django

I have problem with saving files to my server. I need to upload and save files, but id doesn't work for me. I can send file from my UI to my rest api, but i am not able to save the files.
My Models
fs = FileSystemStorage(location='/media/attachments/')
...
class TicketLog(models.Model):
ticket = models.ForeignKey(Ticket, on_delete=models.DO_NOTHING, related_name='ticket_log')
created_date = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.ForeignKey(
User,
on_delete=models.DO_NOTHING,
related_name='ticketlog_creator')
note = models.TextField()
class Meta:
ordering = ['pk']
def __str__(self):
return self.note
class TicketFiles(models.Model):
ticketlog = models.ForeignKey(TicketLog, on_delete=models.CASCADE, related_name='log_file', blank=True, null=True)
attachment = models.FileField(upload_to='attachments', storage=fs)
class UploadWithFile(APIView):
parser_classes = [MultiPartParser, ]
def post(self, request):
content = request.data['file']
data = json.loads(request.data['data'])
queue = Queue.objects.get(pk=data['queue']['id'])
priority = Priority.objects.get(pk=data['priority']['id'])
status = Status.objects.get(pk=data['status']['id'])
created_by = User.objects.get(pk=data['ticket_log'][0]['created_by_id'])
ticket_data = Ticket(
name=data['name'],
priority=priority,
status=status,
queue=queue,
created_date=datetime.datetime.now(),
created_by=created_by
)
ticket_data.save()
log_data = TicketLog(
ticket=ticket_data,
created_date=datetime.datetime.now(),
created_by=created_by,
note=data['ticket_log'][0]['note']
)
log_data.save()
file_data = TicketFiles(
ticketlog=log_data
)
file_data.attachment.save(content.name, content)
file_data.save()
return HttpResponse(content)
It seems that everything works fine and the database is updated correctly, but the files are not saved on server.

An example using rest_framework.parsers ==>> FileUploadParser:
url.py:
urlpatterns = +[
url(r'^docs/$', views.DocumentView.as_view(), name='docs'),
]
models.py
class Document(models.Model):
DOC_CATEGORY = (
('profile_pic', 'Profile_Picture'),
)
class Meta:
ordering = ['uploaded_at']
uploaded_at = models.DateTimeField(auto_now_add=True)
file = models.FileField(blank=False, null=False)
# description
remark = models.CharField(max_length=200, blank=True, null=True)
user = models.ForeignKey(User, blank=True, on_delete=models.DO_NOTHING,)
category = models.CharField(
'Document category',
max_length=64,
choices=DOC_CATEGORY,
default='profile_pic')
def __str__(self):
return self.file.name
serializers.py
class DocumentSerializer(serializers.ModelSerializer):
class Meta():
model = Document
fields = ('file', 'remark', 'uploaded_at', 'user', 'category')
and last views.py:
from rest_framework.parsers import FileUploadParser
class DocumentView(APIView):
http_method_names = ['get', 'post']
model = Document
# fields = ['upload', ]
success_url = reverse_lazy('/')
parser_class = (FileUploadParser,)
# permission_classes = [DocumentViewPerm, ]
# allow any for the example!!!
permission_classes = [AllowAny, ]
def get_object(self, pk):
return serializers.serialize(
'json', list(Document.objects.filter(pk=pk))
)
def get(self, request, pk=None, format=None):
category = request.query_params.get('category', None)
if request.query_params.get('pk'):
return HttpResponse(
self.get_object(pk=request.query_params.get('pk'),
content_type="application/json")
return HttpResponse(self.get_object(request.user.pk),
content_type="application/json")
def post(self, request, *args, **kwargs):
file_serializer = DocumentSerializer(data=request.data)
########################################################
# when uploading keep these headers- no content type !!!
# headers = {'Authorization': '{}'.format(token), 'Accept':
# 'application/json'}
########################################################
if file_serializer.is_valid():
file_serializer.save(user=self.request.user)
# save all fields
# remark # category
remark = request.data.get('remark')
category = request.data.get('category')
return Response(file_serializer.data,
status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
#
EXAMPLE upload a file, open python cmd line
import requests, json
# no content type in headers !!!
headers = {'Accept': 'application/json',
'Authorization': "TOKEN"}
with open('pathtofile', 'rb') as f:
r = requests.post('http://MachineIP/docs/', files={'file': f}, data={'remark': 'my remark'}, headers=headers)

You do not have to write your custom code. Using ModelSerializer, you can achieve what you want.
class TicketFilesSerializer(serializer.ModelSerializer):
class Meta:
model = TicketFiles
fields = '__all__'
You can add other fields that you want according to your requirement.
In your API view,
class YourView(generics.GenericApiView):
serializer_class = TicketFilesSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(request.data)
if serializer.is_valid():
serializer.save()
You have to send data in multipart format.

Related

Django tests AssertionError for update view

I tried to create Django test for UpdateView
but I have such problem as:
self.assertEqual(application.credit_purpose, 'House Loan')
AssertionError: 'Car Loan' != 'House Loan'
Car Loan
House Loa
def test_application_update(self):
application = Application.objects.create(customer=self.customer, credit_amount=10000, credit_term=12,
credit_purpose='Car Loan', credit_pledge=self.pledge,
product=self.product,
number_request=2, date_posted='2020-01-01', reason='None',
repayment_source=self.repayment, possible_payment=1000,
date_refuse='2020-01-02', protokol_number='123457',
status=self.status,
language=0, day_of_payment=1, credit_user=self.user)
response = self.client.post(
reverse('application_update', kwargs={'pk': application.id}),
{'credit_purpose': 'House Loan'})
self.assertEqual(response.status_code, 200)
application.refresh_from_db()
self.assertEqual(application.credit_purpose, 'House Loan')
This is my model
class Application(AbstractCredit):
number_request = models.IntegerField(verbose_name='Номер заявки', unique=True, default=number_auto) # Добавить автоинкремент
date_posted = models.DateField(verbose_name='Дата заявки', auto_now_add=True)
reason = models.CharField(max_length=200, null=True, blank=True, verbose_name='Причина отказа/Условия одобрения')
repayment_source = models.ForeignKey(Repayment, on_delete=models.CASCADE, verbose_name='Источник погашения')
possible_payment = models.IntegerField(verbose_name='Желаемая сумма ежемесячного взноса')
date_refuse = models.DateField(default=one_day_more, null=True, blank=True, verbose_name='Дата отказа/одобрения')
protokol_number = models.CharField(max_length=20, unique=True, null=True, blank=True,
verbose_name='Номер протокола')
status = models.ForeignKey(Status, on_delete=models.CASCADE, default=1, verbose_name='Статус')
language = models.IntegerField(choices=LANGUAGES_CHOICES, verbose_name='Язык договора', blank=True, null=True)
day_of_payment = models.IntegerField(choices=DAY_OF_PAYMENT_CHOICES,
verbose_name='Предпочитаемый день оплаты по кредиту')
credit_user = models.ForeignKey(User, on_delete=models.SET(0), verbose_name='Кредитный специалист')
This is my view
class ApplicationUpdate(BasePermissionMixin, SuccessMessageMixin, UpdateView):
model = Application
form_class = ApplicationForm
template_name = 'standart_form.html'
permission_required = 'Изменение заявки'
success_message = 'Заявка успешно изменена'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['button_name'] = 'Сохранить'
return context
def get_success_url(self):
return reverse_lazy('application_detail', kwargs={'pk': self.get_object().id})
So, I also got stuck in this for a while. The main problem is at:
...
response = self.client.post(
reverse('application_update', kwargs={'pk': application.id}),
{'credit_purpose': 'House Loan'}
)
...
I was able to understand thanks to this answer. It happens because you are posting to a form and it expects to have all fields filled, otherwise it will fail validation and thus will not call .save().
What you can do is, after creating the object copy its data into a dictionary, then modify it before posting:
tests.py
from django.forms.models import model_to_dict
class ApplicationTestCase(TestCase):
def setUp(self):
customer = Customer.objects.create(...)
...
self.data = {
'customer': customer, 'credit_amount': 10000, 'credit_term': 12,
'credit_purpose': 'Car Loan', ...
}
def test_application_update(self):
application = Application.objects.create(**self.data)
post_data = model_to_dict(application)
post_data['credit_purpose'] = 'House Loan'
response = self.client.post(
reverse(
'app:view-name', kwargs={'pk': application.id}),
post_data
)
# print(application.credit_purpose)
# application.refresh_from_db()
# print(application.credit_purpose)
# It returns a redirect so code is 302 not 200.
self.assertEqual(response.status_code, 302)
self.assertEqual(application.credit_purpose, 'House Loan')
Also, get_absolute_url() is set in the wrong place should be under models:
models.py
class Application(AbstractCredit):
...
def get_absolute_url(self):
return reverse('app:view-name', kwargs={'pk': self.pk})

Swagger provide correct render of POST method API from serializer, but not for DELETE method

Our project is using Django, Django-Rest-Framework, and drf_yasg to document our API with swagger. I created a serializer, and I'm using it with my POST method. everything works well, all the fields are present on my swagger page.
I created a delete method using the same serializer and the swagger documentation is showing as parameters to the API only the first group of fields.
I tried to use
#swagger_auto_schema(
method='post',
operation_description="POST /Rules/list/",
responses={200: listResponseSerializer()},
)
#swagger_auto_schema(
#swagger_auto_schema(responses={200: listResponseSerializer()})
method='delete',
operation_description="DELETE /Rules/list/",
responses={200: listResponseSerializer()},
)
#action(detail=False, methods=['post', 'delete'])
and have a single method. both, the post and delete methods are immediately absent from the documentation page.
and when using
#swagger_auto_schema(responses={200: listResponseSerializer()})
Post method is working, but not the delete method.
this capture shows the good results from POST method, where all the fields are presents in the API documentation. filters, parameters, and others.
With DELETE, it shows only the filters and ignores all the other fields present in the serializer.
Hope someone can help me with this one. I'm struggling for a couple of days now with it. I don't catch what I'm missing or what piece of the integration between django and swagger I don't understand, and need help.
views.py:
class ActionAPIView(generics.GenericAPIView):
"""
Base class for all Action views so the filtering is automatically inserted
"""
filter_class = RESTRuleFilter
def get_queryset(self):
return Rule.objects.allowed_to_user(self.request.user)
def filter_queryset(self, queryset):
serializer = self.get_serializer(data=self.request.data)
if serializer.is_valid():
if 'filter' in serializer.validated_data:
filter = RESTRuleFilter(serializer.validated_data['filter'], queryset=queryset)
return filter.qs
return queryset
def get_serializer(self, *args, **kwargs):
return listSerializer(*args, **kwargs)
class listItemsApi(ActionAPIView):
"""
post:
add one or more Items to selected Rule list
delete:
remove one or more Items to selected Rule list
"""
def get_queryset(self):
return Rule.objects.allowed_to_user(self.request.user)
def get_serializer(self, *args, **kwargs):
return listPostSerializer(*args, **kwargs)
def get_delete_serializer(self, *args, **kwargs):
return listSerializer(*args, **kwargs)
def get_channel(self, list_id):
bu_list = []
for i in list_id:
bu = Channel.objects.allowed_to_user(self.request.user).get(
pk=i
)
bu_list.append({"pk": bu.id, "name": bu.name})
return bu_list
#swagger_auto_schema(responses={200: listResponseSerializer()})
def post(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
selected_Rules = serializer.get_selected_Rules(queryset)
bu_ids, cluster_list, Rules_id = self.build_sets(selected_Rules)
bu = self.get_channel(bu_ids)
task_id, timestamp = "sample_task_001"
response = {
"channels": bu,
"task_id": task_id,
}
return Response(response, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
in serializer.py:
class listItemsSerializer(serializers.Serializer):
block_item = serializers.CharField(required=False, allow_blank=True)
never_log_item = serializers.BooleanField(required=False, default=False)
never_learn_item = serializers.BooleanField(required=False, default=False)
description = serializers.CharField(required=False, allow_blank=True)
class listFiltersSerializer(serializers.Serializer):
name = serializers.CharField(required=False, allow_blank=True)
cluster = serializers.CharField(required=False, allow_blank=True)
channel = serializers.CharField(required=False, allow_blank=True)
partition = serializers.CharField(required=False, allow_blank=True)
class listSerializer(serializers.Serializer):
filters = listFiltersSerializer(required=False, help_text="list of Rules filters")
items = serializers.ListField(child=serializers.CharField(), required=True)
selected = serializers.ListField(child=serializers.IntegerField(), required=False, write_only=True)
select_all = serializers.BooleanField(
default=False,
write_only=True,
help_text=("Set to True to select all objects"),
)
def validate(self, data):
"""
Check if either both selected_status and select_all are configured or
we have a list of selected entries in selected
"""
user = get_current_user()
if data["select_all"]:
return data
if data["select_all"] is False and (
"selected" not in data or not data["selected"]
):
raise serializers.ValidationError(
"You must specify either select_all or selected"
)
found_count = 0
if "selected" in data and data["selected"]:
for d in data["selected"]:
if Rule.objects.allowed_to_user(user).filter(pk=d).exists():
found_count += 1
if found_count != len(data["selected"]):
raise serializers.ValidationError(
"Some of the selected items are invalid"
)
return data
def get_selected_Rules(self, queryset=None):
if queryset is None:
queryset = Rule.objects.all()
if self.validated_data.get('select_all', False):
return queryset
else:
return queryset.filter(pk__in=self.validated_data['selected'])
class listPostSerializer(listSerializer):
params = listItemsSerializer(
required=True, help_text="list of Itemss and setting to apply on selected Rules"
)
filters.py:
class RESTRuleFilter(RESTFilterSet):
name = django_filters.CharFilter(
lookup_expr="icontains", help_text="Rule name containing"
)
cluster = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all()
)
channel = django_filters.ModelMultipleChoiceFilter(
queryset=Channel.objects.all(), method="subfilter"
)
partition = django_filters.CharFilter(
lookup_expr="icontains", help_text="Partition name containing"
)
enforcement_mode = django_filters.CharFilter(
lookup_expr="icontains",
label="Enforcement Mode",
help_text="Enforcement Mode containing",
)
signatureset_name = django_filters.CharFilter(
label="Rule containing a signature set matching this string",
field_name="signatureset",
method="subfilter",
)
class Meta:
model = Rule
fields = (
"name",
"cluster",
"channel",
"partition",
"xxxxxxx",
"yyyyyyy",
)
urls.py:
schema_view = get_schema_view(
openapi.Info(
title="WAF Management Portal API",
default_version="v1",
description="REST api for interaction between Frontend and Backend.",
contact=openapi.Contact(email="xxx#email.com"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path(
"",
schema_view.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path(
"rule/", ruleListAPIView.as_view(), name="rule_list_view_api"
),
path(
"rules/list/", listItemsApi.as_view(), name="list_api"
),
]
models.py:
class Channel(models.Model):
name = models.CharField(max_length=1024, db_index=True, unique=True)
class Cluster(models.Model):
name = models.CharField(max_length=1024, db_index=True, unique=True)
channel = models.ForeignKey(
Channel, on_delete=models.SET_NULL, null=True
)
class rule(models.Model):
app_id = models.CharField(max_length=32, db_index=False)
cluster = models.ForeignKey(
Cluster, on_delete=models.CASCADE, related_name="rules", null=True
)
name = models.CharField(max_length=1024, db_index=True)
partition = models.CharField(max_length=128, db_index=True)
objects = ruleManager()
class Meta:
verbose_name_plural = "rules"
unique_together = ("app_id", "cluster")
indexes = [models.Index(fields=["app_id", "cluster"])]
def __str__(self):
return self.full_path

django-extra-views expose InlineFormSet field values into get_context_data() and forms_valid() methods

I would like to get access to fields of the InlineFormSet for consumption into the View.
Here is an example based on https://django-extra-views.readthedocs.io/en/latest/views.html#createwithinlinesview-and-updatewithinlinesview:
from extra_views import InlineFormSet, CreateWithInlinesView,
class ItemsInline(InlineFormSet):
model = Item
class TagsInline(InlineFormSet):
model = Tag
class OrderCreateView(CreateWithInlinesView):
model = Order
inlines = [ItemsInline, TagsInline]
def get_success_url(self):
return self.object.get_absolute_url()
How do I expose the InlineFormSet fields in the get_context_data() and forms_valid() methods?
Warning: Code below fails!
class OrderCreateView(CreateWithInlinesView):
[... see above ...]
def get_context_data(self, **kwargs):
context = super(OrderCreateView, self).get_context_data(**kwargs)
if self.request.POST:
context['items'] = ItemsInline(self.request.POST)
context['tags'] = TagsInline(self.request.POST)
else:
context['items'] = ItemsInline()
context['tags'] = TagsInline()
return context
def forms_valid((self, form, inlines):
[...]
return super(OrderCreateView, self).forms_valid(form, inlines)
I reproduced your example with this models:
STATUS_CHOICES = (
(0, 'Placed'),
(1, 'Charged'),
(2, 'Shipped'),
(3, 'Cancelled'),
)
class Order(models.Model):
name = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
action_on_save = models.BooleanField(default=False)
class Item(models.Model):
item_name = models.CharField(max_length=255)
sku = models.CharField(max_length=13)
price = models.DecimalField(decimal_places=2, max_digits=12, db_index=True)
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
status = models.SmallIntegerField(default=0, choices=STATUS_CHOICES, db_index=True)
date_placed = models.DateField(default=now, null=True, blank=True)
def __unicode__(self):
return '%s (%s)' % (self.item_name, self.sku)
class Tag(models.Model):
tag_name = models.CharField(max_length=255)
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag_name
Urls:
from django.conf.urls import url
from .views import OrderCreateView, OrderUpdateView
urlpatterns = [
url(r'^inlines/new/$', OrderCreateView.as_view()),
url(r'^inlines/(?P<pk>\d+)/$', OrderUpdateView.as_view()),
]
Forms:
from django import forms
from .models import Order, Item
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ['name']
def save(self, commit=True):
instance = super(OrderForm, self).save(commit=commit)
if commit:
instance.action_on_save = True
instance.save()
return instance
class ItemForm(forms.ModelForm):
flag = forms.BooleanField(initial=True)
class Meta:
model = Item
fields = ['item_name', 'sku', 'price', 'order', 'status']
Views:
from django.contrib.contenttypes.models import ContentType
from extra_views import InlineFormSet, CreateWithInlinesView, UpdateWithInlinesView
from extra_views.generic import GenericInlineFormSet
from .forms import OrderForm
from .models import Item, Order, Tag
class ItemsInline(InlineFormSet):
model = Item
fields = ['item_name', 'sku', 'price', 'order', 'status']
class TagsInline(GenericInlineFormSet):
model = Tag
fields = ['tag_name']
class OrderCreateView(CreateWithInlinesView):
model = Order
fields = ['name']
context_object_name = 'order'
inlines = [ItemsInline, TagsInline]
template_name = 'extra_views/order_and_items.html'
def get_success_url(self):
return '/inlines/%i' % self.object.pk
class OrderUpdateView(UpdateWithInlinesView):
model = Order
form_class = OrderForm
inlines = [ItemsInline, TagsInline]
template_name = 'extra_views/order_and_items.html'
def get_success_url(self):
return ''
In OrderCreateView you cannot read Tag and Item records in the
get_context_data method because they haven't been created.
In OrderCreateView you can read all records after the forms_valid
method as usual.
In OrderUpdateView you can read all records in the
get_context_data method like in the forms_valid method.
It looks like this:
class OrderCreateView(CreateWithInlinesView):
# ...
def get_context_data(self, **kwargs):
data = super(OrderCreateView, self).get_context_data(**kwargs)
from pprint import pprint
pprint(self.request.POST) # there is only post data here
return data
def forms_valid(self, form, inlines):
instance = super(OrderCreateView, self).forms_valid(form, inlines)
ct = ContentType.objects.get_for_model(self.model)
print('all items', [item.item_name for item in self.object.items.all()]) # items
print('all tags', [tag.tag_name for tag in
TagsInline.model.objects.filter(content_type=ct, object_id=self.object.id)]) # tags
return instance
class OrderUpdateView(UpdateWithInlinesView):
# ...
def get_context_data(self, **kwargs):
context = super(OrderUpdateView, self).get_context_data(**kwargs)
ct = ContentType.objects.get_for_model(self.model)
print('all items', [item.item_name for item in self.object.items.all()]) # items
print('all tags', [tag.tag_name for tag in
TagsInline.model.objects.filter(content_type=ct, object_id=self.object.id)]) # tags
return context

How do I add a field to a django-rest-framework serializer that isn't on my model?

I have a serializer that works fine for the GET, POST, DELETE actions. It exposes the model fields that I want. However for the PUT action, the user will send back values that aren't built into my models and the server will deal with how to perform the update on the model. I can send the data back using Postman or Curl and it works but the browseable API still looks like this:
For the PUT method I want "is_winner", "num_hands_won", and "score" to show up instead of the actual model fields. How do I do this? (Let me know in the comments if you need more info)
StatisticsSerializer:
class StatisticsSerializer(serializers.ModelSerializer):
# pk = serializers.IntegerField(required=False)
class Meta:
model = Statistics
fields = [
'url',
'games_won',
'hands_won',
'games_played',
'high_score',
'low_score',
]
Statistics Model:
class Statistics(models.Model):
# Define model fields:
user = models.OneToOneField(User, primary_key=True)
games_won = models.IntegerField(null=True, blank=True)
hands_won = models.IntegerField(null=True, blank=True)
games_played = models.IntegerField(null=True, blank=True)
high_score = models.IntegerField(null=True, blank=True)
low_score = models.IntegerField(null=True, blank=True)
def __str__(self):
return str(self.pk)
def increment_games_won(self, is_winner):
if is_winner is True:
self.games_won = self.games_won + 1
return self.games_won
def add_to_hands_won(self, num_hands_won):
if num_hands_won > 0 and num_hands_won < 8:
self.hands_won = self.hands_won + num_hands_won
return self.hands_won
def increment_games_played(self):
self.games_played = self.games_played + 1
return self.games_played
def new_high_score(self, score):
if score > self.high_score:
self.high_score = score
return self.high_score
def new_low_score(self, score):
if score < self.low_score:
self.low_score = score
return self.low_score
Statistics ViewSet:
class StatisticsViewSet(DefaultsMixin, viewsets.ModelViewSet):
queryset = Statistics.objects.all()
serializer_class = StatisticsSerializer
filter_class = StatisticsFilter
search_fields = ('pk', 'user')
ordering_fields = ('games_won', 'hands_won', 'games_played', 'high_score', 'low_score')
def update(self, request, pk=None):
stats = self.get_object()
stats.increment_games_won(request.data['is_winner'])
stats.add_to_hands_won(request.data['num_hands_won'])
stats.increment_games_played()
stats.new_low_score(request.data['score'])
stats.new_high_score(request.data['score'])
stats.save()
serialized_stats = StatisticsSerializer(stats, context={'request': request}).data
return Response(serialized_stats)
You could probably use another Serializer and use it for you PUT API
StatisticsUpdateSerializer:
class StatisticsUpdateSerializer:
is_winner = ...
num_hands_won = ...
score = ...
And use this serializer in the PUT API or create a new route as shown in the example mentioned in the DRF documentation here
#detail_route(methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
// Use your serializer below
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)

django rest framework POST request fails with nested serialization

My POST request to url http://127.0.0.1:8000/airlines/ fails
Here are models and corresponding serializers in my project. Initially I want to create Airlines information and then add flights later
Can someone please let me know where am I going wrong
models.py
class AirlineFirm(models.Model):
operator_name = models.CharField(max_length=30)
def __unicode__(self):
return "%s" % (self.operator_name)
class Flight(models.Model):
flight_num = models.CharField(max_length=7, primary_key=True)
src = models.CharField(max_length=20)
dest = models.CharField(max_length=20)
outbound_time = models.DateTimeField()
inbound_time = models.DateTimeField()
num_of_seats = models.IntegerField()
ticketPrice = models.FloatField()
delayed = models.BooleanField()
airlinefirm = models.ForeignKey(AirlineFirm)
serializers.py
class FlightSerializer(serializers.Serializer):
flight_num = serializers.CharField(read_only=True)
src = serializers.CharField()
dest = serializers.CharField()
outbound_time = serializers.DateTimeField()
inbound_time = serializers.DateTimeField()
num_of_seats = serializers.IntegerField()
ticketPrice = serializers.FloatField()
delayed = serializers.BooleanField()
airlinefirm = serializers.RelatedField(read_only='True')
#passengers = models.ManyToManyField(Passenger)
def create(self, validated_data):
return Flight.objects.create(**validated_data)
def update(self, instance, validated_data):
pass
class AirlineSerializer(serializers.ModelSerializer):
flights = FlightSerializer(many=True)
class Meta:
model = AirlineFirm
fields = ('operator_name','flights')
def create(self, validated_data):
flights_data = validated_data.pop('flights')
airlinefirm = AirlineFirm.objects.create(**validated_data)
for flight_data in flights_data:
Flight.objects.create(airlinefirm=airlinefirm, **flight_data)
return airlinefirm
views.py
#api_view(['GET','POST'])
def airline(request, format=None):
if request.method == 'GET':
airlines = AirlineFirm.objects.all()
serializer = AirlineSerializer(airlines, many=True)
return Response(serializer.data)
if request.method == 'POST':
#data = JSONParser().parse(request)
serializer = AirlineSerializer(data=request.data)
#import pdb;pdb.set_trace()
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
import pdb;pdb.set_trace()
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When I send a POST request http://127.0.0.1:8000/airlines/ to my airlines view class I get 404 response
http request
import json
import requests
payload = {'operator_name':'AmericanAirlines','flights':[]}
headers = {'Content-type':'application/json'}
r = requests.post('http://127.0.0.1:8000/',data=json.dumps(payload),headers=headers)
Here is the error message:
AttributeError: Got AttributeError when attempting to get a value for field flights on serializer AirlineSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the AirlineFirm instance.
Original exception text was: 'AirlineFirm' object has no attribute 'flights'.
[18/Feb/2016 13:43:58] "POST /airlines/ HTTP/1.1" 500 112039
You need to and an endpoint to the api in your urls.py if it's not there, then point to it in your request, like:
r = requests.post('http://127.0.0.1:8000/airlines',data=json.dumps(payload),headers=headers)