Django Forms with ReactJS - django

I have Django forms rendered using Django templates. Now I want to add a React component to one of the form fields (and perhaps to more than one field in the long term).
Based on what I have read so far, its seem best not to mix Django templating with React rendering and have Django serve only as backend API sending JSON data to React, while React takes over the entire form rendering. So I am now trying to re-render my forms entirely through React.
Instead of forms.py, I have now created serializers.py to define what data is to be sent to React and have Django Rest Framework setup in my environment. Now I am trying to figure out how to send this data across. There are some good online tutorials (and SO posts) that talk about integrating Django/DRF with React but havent found a single example of end-to-end form rendering through React and DRF.
Specifically, can anyone let me know what do I really write in my view that can then be useful for the GET request from React that tries to fetch the form data? A web reference or just the broad steps needed should be enough for me to get started (and to dig in more into the docs).
Update:
Also adding a simplified version of the serializers.py code here:
from .models import Entity
from rest_framework import serializers
class EntitySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Entity
fields = ['name', 'role', 'location']

First, i think you need to check related React documentation about forms with multiple inputs. It gives you base idea about how things should be structured in React side.
About fetching data from server, you can try something like this in componentDidMount:
componentDidMount() {
// Assuming you are using jQuery,
// if not, try fetch().
// Note that 2 is hardcoded, get your user id from
// URL or session or somewhere else.
$.get('/api/profile/2/', (data) => {
this.setState({
formFields: data.fields // fields is an array
});
});
}
Then you can create your html input elements in render method with something like this:
render () {
let fields = this.state.formFields.map((field) => {
return (
<input type="text" value={field.value} onChange={(newValue) => {/* update your state here with new value */ }} name={field.name}/>
)
});
return (
<div className="container">
<form action="">
{fields}
<button onClick={this.submitForm.bind(this)}>Save</button>
</form>
</div>
)
}
And here is your submitForm method:
submitForm() {
$.post('/api/profile/2/', {/* put your updated fields' data here */}, (response) => {
// check if things done successfully.
});
}
Update:
Here is an untested-but-should-work example for your DRF view:
from rest_framework.decorators import api_view
from django.http import JsonResponse
from rest_framework.views import APIView
class ProfileFormView(APIView):
# Assume you have a model named UserProfile
# And a serializer for that model named UserSerializer
# This is the view to let users update their profile info.
# Like E-Mail, Birth Date etc.
def get_object(self, pk):
try:
return UserProfile.objects.get(pk=pk)
except:
return None
# this method will be called when your request is GET
# we will use this to fetch field names and values while creating our form on React side
def get(self, request, pk, format=None):
user = self.get_object(pk)
if not user:
return JsonResponse({'status': 0, 'message': 'User with this id not found'})
# You have a serializer that you specified which fields should be available in fo
serializer = UserSerializer(user)
# And here we send it those fields to our react component as json
# Check this json data on React side, parse it, render it as form.
return JsonResponse(serializer.data, safe=False)
# this method will be used when user try to update or save their profile
# for POST requests.
def post(self, request, pk, format=None):
try:
user = self.get_object(pk)
except:
return JsonResponse({'status': 0, 'message': 'User with this id not found'})
e_mail = request.data.get("email", None)
birth_date = request.data.get('birth_date', None)
job = request.data.get('job', None)
user.email = e_mail
user.birth_date = birth_date
user.job = job
try:
user.save()
return JsonResponse({'status': 1, 'message': 'Your profile updated successfully!'})
except:
return JsonResponse({'status': 0, 'message': 'There was something wrong while updating your profile.'})
And this is related url for this view:
urlpatterns = [
url(r'^api/profile/(?P<pk>[0-9]+)/$', ProfileFormView.as_view()),
]

Related

django.db.utils.IntegrityError when sending post request with axios in vue

im new to web development so please explain your solution
i am trying to send data to an endpoint in the api i created with the django rest framework in vue with axios but whenever i do i get this error : django.db.utils.IntegrityError: NOT NULL constraint failed: main_checkbox.label
(i know im not building the api the way its supposed to be built but thats another problem)
i can make a get request with axios no problem and i can even send post data via an html form with no problem (but i dont want to cause im creating a single page application with vue and don't want it to refresh when submitting)
here is the vue code :
<template>
<div class="home">
<input type="text" name="label" id="" v-model="label" />
<input type="submit" value="Create" #click="createCheckbox" />
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
label: "",
};
},
methods: {
async createCheckbox() {
let result = axios.post("http://127.0.0.1:8000/create/", {
label: this.label,
});
console.log(result);
},
},
};
</script>
the django views code :
from rest_framework.response import Response
from rest_framework.decorators import api_view
from main.models import checkbox
from main.serializers import checkboxSerializer
#api_view(['POST'])
def create(request):
checkboxobj = checkbox.objects.create(
state=False, label=request.POST.get('label'))
serialized = checkboxSerializer(checkboxobj)
return Response(serialized.data)
django models :
from django.db import models
# Create your models here.
class checkbox(models.Model):
label = models.CharField(max_length=50)
state = models.BooleanField(default=False)
def __str__(self):
return self.label
django serializers :
from rest_framework.serializers import ModelSerializer
from main.models import checkbox
class checkboxSerializer(ModelSerializer):
class Meta:
model = checkbox
fields = '__all__'
One of the features of serializers is that it validates data the user sends to the server. ModelSerializer can fetch validated data and create a record if you use save() method after validation with is_valid(). So, in your views.py:
#api_view(['POST'])
def create(request):
serializer = checkboxSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serialized.data)
else:
return Response(serializer.errors)
As the error states, the issue is not that the POST request is failing. Rather the error is related to the database. When inserting the record with checkbox.objects.create. The label field is null while creating the record. It could be the front end is sending a null value for the label or there is an error parsing the value from the POST request.
Since you are using DRF, I would suggest using CreateAPIView, it will manage the parsing and inserting for you.
This should do the backend part:
class Checkbox(models.Model):
label = models.CharField(max_length=50)
state = models.BooleanField(default=False)
def __str__(self):
return self.label
class CheckboxSerializer(ModelSerializer):
class Meta:
model = Checkbox
fields = '__all__'
class CreateCheckBox(CreateAPIView):
serializer_class = CheckboxSerializer

How to write tests for custom Django generic view?

I didn't understand the way we should write tests for Django views. Because, my case felt different than others.
I have a view, let's say MyView, inherits from CreateView of Django generic class based views. But, in the background, I mean in the form_valid function, this view tries to connect to a remote server with a Python library, then fetches data according the input of the user, then process other details and saves the object if everything is OK, if not, it returns custom errors.
What should be my way to handle this kind of view so I can write a test?
(I can use something like that but it's not good to handle possible errors:
from django.test import Client
c = Client()
c.login(mydetails..)
y = c.post('/url/', {'my': 'data'})
It works, yes, but I only check the status code in this case. y.status_code can't help, because Django returns 200 even if there is an error in the form.
To simplify testing framework in django, the test will simulate client request (like a user clicked on a link "url") this url is associated with a view which will handle the request and return a response to the user. for example in your test you will assert if the view renders the correct response and template. let suppose that we have a view for creating a blog, how might be tested? see the code below.
views.py
def createblog_view(request):
if request.method == 'POST':
form = BlogForm(request.POST)
if form.is_valid():
blog = form.save(commit=False)
blog.author = request.user # save the user who created the blog
blog.save()
return redirect(blog.get_absolute_url())
else:
form = BlogForm()
context = {'form': form}
return render(request, 'blog/createblog.html', context)
test.py
class BlogTest(TestCase):
def setUp(self):
# this user will be used to create the blog
self.user = User.objects.create_superuser(
'foo',
'foo#test.com',
'password'
)
def test_create_blog(self): # create update and delete a blog
# log user in and user
self.client.login(username='foo', password='password')
# create new blog
# expected date from the user, you can put invalid data to test from validation
form_data = {
'title': 'new test blog',
'body': 'blog body'
}
form = BlogForm(data=blogform_data) # create form indstance
"""
simulate post request with self.client.post
/blog/createblog/ is the url associated with create_blog view
"""
response = self.client.post('/blog/createblog/', form_data)
# get number of created blog to be tested later
num_of_blogs = Blog.objects.all().count()
# get created blog
blog = Blog.objects.get(title=form_data['title'])
# test form validation
self.assertTrue(blogform.is_valid())
# test slugify method, if any
self.assertEqual(blog.slug, 'new-test-blog')
# test if the blog auther is the same logged in user
self.assertEqual(blog.author, self.user1)
# one blog created, test if this is true
self.assertEqual(num_of_blogs, 1)
# test redirection after blog created
self.assertRedirects(
createblog_response,
'/blog/new-test-blog/',
status_code=302,
target_status_code=200
)

Delete Django model with AJAX call

I am writing a photogallery app, which is using Django Rest Framework API.
I fill the data in Django models (images and descriptions) with said API using AJAX (specifically axios).
The question is - is it possible not only to POST data, but to delete it from my frontend app and I mean model instance - like it's done in the admin interface or even delete all model instances.
Yes, you can for example add a .delete(..) implementation as well:
class SomeModelDetail(APIView):
def get_object(self, pk):
try:
return SomeModel.objects.get(pk=pk)
except SomeModel.DoesNotExist:
raise Http404
# ...
def delete(self, request, pk, format=None):
self.get_object(pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
The HTTP_204_NO_CONTENT response is thus a status code that is sometimes used to indicate that the corresponding object is now removed.
If we then add this view to the URL:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from app import views
urlpatterns = [
url(r'^somemodel/(?P<pk>[0-9]+)/$', views.SomeModelDetail.as_view()),
]
format_suffix_patterns(urlpatterns)
We can then make a DELETE AJAX call:
# JavaScript (Ajax request)
$.ajax({
url: 'http://localhost:8080/someapp/somemodel/123/',
type: 'DELETE',
data: {},
contentType:'application/json',
dataType: 'text',
error: function(result){},
success: function(result) {}
});
We thus perform a DELETE method request, for a specific somemodel instance (here with primary key 123, although you have to fill that in yourself with a sensical one).

django-rest-swagger can't seem to work for me. I can't get it to document anything beyond a title

It seems like django-rest-swagger dropped support for the YAML documentation, and replaced it with a vague non-documented way to do things. I've spent the last 48 hours trying to understand how I can have it document the parameters that go into my post methods.
For instance: I have this:
class user_addresses(APIView):
"""
get all addresses or post a new one
"""
authentication_classes = ([JSONWebTokenAuthentication])
def get(self, request, format=None):
addresses = Address.objects.filter(owner_id=request.user.id)
print (addresses)
serializer = address_serializer(addresses, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = address_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'success': True,
'result': serializer.validated_data},
status=status.HTTP_201_CREATED)
return Response({'success': False,
'result': serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
But the django-rest-swagger will show it as:
Can someone point me in the direction of something that works where I can define all the rich data that swagger allows, like the post field names, if they're mandatory or not. etc. i'm just going crazy here running in circle and can't find anything but complaints that there's no way to do this.
So the idea with the 2.0 update was to use CoreAPI, the "internal" rest framework schema generation, and from it generate the swagger spec.
CoreAPI uses serializer and view classes to do its thing. From serializers it knows what fields are required, what type are those fields and if you want to add your personal description you can do so with help_text parameter:
some_field = serializers.Field(help_text='Field description')
In your case, the problem will be that it won't be able to understand the relationship between the APIView and your serializer. I suggest to take an extra step and move to generic views or viewsets, all of them support serializer_class attribute that can be used for the introspection. For your example something like this should work:
# serializer
class AddressSerializer(serializers.ModelSerializer):
line1 = serializers.CharField(help_text='Field documentation!')
class Meta:
model = Address
fields = '__all__'
read_only_fields = 'owner',
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
# api class-based view
class UserAddresses(generics.ListCreateAPIView):
"""
General API documentation (not wisible in the swagger view)
get:
GET-specific documentation!
Lorem ipsum
post:
POST-specific documentation!
Dolor **sit amet**
"""
authentication_classes = ([JSONWebTokenAuthentication])
permission_classes = permissions.IsAuthenticated,
serializer_class = AddressSerializer
def get_queryset(self):
return Address.objects.filter(owner_id=self.request.user.id)
For views there is a specific docstirng format, it's very simple and hopefully, will improve overtime. In any way, you should have a bit more acceptable result now:
A CoreAPI Document can help you make a custom Swagger view. Swagger takes a coreapi json input to render the view - Django Rest Swagger uses the Python bindings of CoreAPI to generate that JSON (https://github.com/core-api/python-client).
What does the coreapi.Document object contain?
For each API, you can create a coreapi.Link() object.
Each Link object contains:
A URL
HTTP Method
Description
Fields
The list of fields must contain coreapi.Field() objects. A Field object has the parameters:
Name
Required (whether the field is mandatory)
Location (path parameter or query parameter)
Description
An Example
A sample Swagger Schema would look something like this, if we were to use CoreAPI:
import coreapi
def api_schema_generator():
api_schema = coreapi.Document(
title="My Swagger",
content={
"User Addresses": {
"int_api_get": coreapi.Link(
url="/int_api/v1/addresses/",
action="get",
description="Get addresses of a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user whose addresses are to be found"
),
]
),
"int_api_post": coreapi.Link(
url="/int_api/v1/addresses/",
action="post",
description="Add address for a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user"
),
coreapi.Field(
name="address",
required=True,
location="path",
description="Address of the user"
),
]
)
}
}
)
return api_schema
Our view would take this coreapi.Document object as input. We use the SwaggerUIRenderer, OpenAPIRenderer and CoreJSONRenderer decorators for our view.
views.py:
from rest_framework.decorators import api_view, renderer_classes
from rest_framework_swagger import renderers as swagger_renderer
from rest_framework import renderers
#api_view()
#renderer_classes([renderers.CoreJSONRenderer,
swagger_renderer.OpenAPIRenderer,
swagger_renderer.SwaggerUIRenderer,
])
def schema_view(request):
api_schema = api_schema_generator()
return response.Response(api_schema)
All that we need now is a URL mapping for our view.
urls.py:
from django.conf.urls import include, url
urlpatterns = [
url(r'^$', views.schema_view),
]
Writing a custom swagger might seem slightly tedious, but you have complete control over what data you want to expose in your Swagger View.

How would I create a CBV using django rest framework which will either retrieve a model instance or create a new one?

I have a person model with the fields first_name, last_name and email. I'd like to send these fields to a view that would check the DB for an existing instance. If there is one the ID will be returned, if not a new instance will be created and the new ID returned. Using a standard FBV I would do this (cut down version, minus validation etc):
from django.http import HttpResponse
from mysite.models import Person
import json
def get_or_create_person(request):
try:
person = Person.objects.get(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
except Person.DoesNotExist:
person = Person(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
person.save()
response = {'id': person.id}
return HttpResponse(json.dumps(response))
Is there any real point in converting it to use a CBV and tie it in with the rest framework and if so how would I go about doing it? The main reason I want it as a CBV is so I can use mixins etc that I already include in other parts of my app.
Edit: I thought about using the ViewSets available through the rest framework but they split this functionality into GET and POST methods, with get retrieving a record and post either updating or creating one. I basically need a view which can accept either.
I had to kinda combine the get and post functions as follows:
class GetOrCreateCustomerView(APIView):
'''
API endpoint that takes an email address, first name & surname and then
either returns the matching customer id or creates a new customer and
returns that id
'''
required_fields = ('first_name', 'last_name', 'email')
def get(self, request, format=None):
request_data = request.GET
response = self.get_customer_id(request_data)
return JSONResponse(response)
def post(self, request, format=None):
request_data = request.POST
response = self.get_customer_id(request_data)
return JSONResponse(response)
def get_customer_id(self, data):
kwargs = {}
for f in self.required_fields:
if f in data:
kwargs[f] = data[f]
else:
return None
try:
customer = Customer.objects.get(**kwargs)
except Customer.DoesNotExist:
customer = Customer(**kwargs)
customer.save()
if customer.id is not None:
response = {'customer_id': customer.id}
else:
response = {
'error': 'Please provide a first name, surname and email address'
}
return response
The JSONResponse referenced here is the one from the rest framework docs.
for classbased views in django-rest-framework this is usually done by:
a GET request to the resource will retrieve records
a POST request to the same resource will create a record
I believe there are a couple examples of this in the django rest framework documentation