how to get new data at run time from django? - reactjs

I am working on a Django project with ReactJs frontend. I have to built a simple chat application in that project where users can communicate with each other.
in django views I have following function to read messages
Views.py
class MessagesListAPI(GenericAPIView, ListModelMixin ):
def get_queryset(self):
condition1 = Q(sender=15) & Q(receiver=11)
condition2 = Q(sender=11) & Q(receiver=15)
return Messages.objects.filter(condition1 | condition2)
serializer_class = MessagesSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.list(request, *args, **kwargs)
this gives me all the messages between user 11 and user 15.
on frontend I am getting these messages from rest Api by calling above function
frontend
const chatApi = axios.create({
baseURL: 'http://localhost:8000/chat/'
})
const getMessages = async() => {
let data = await chatApi.get(`MessageRead/`).then(({data})=>data);
setMessages(data);
}
I am calling this function in an onClick event of button. and displaying the messages by maping messages array.
problem is that at the time when these messages are open to the user. at that time if a new message(data) is added in database. I have to press that button again and call getMessages function again to display that message.
Is there a way to automatically get that new message(record) without calling this function again by pressing a button?

Related

How to implement simple notifications in drf + react?

Normally in django with templates I implement basic notifications like this.
For example.
class Article(models.Model):
name = models.CharField()
owner = models.ForeignKey(User)
class Comment():
article = models.ForeignKey(Article)
txt = models.CharField()
user = models.ForeginKey()
datetime = models.DateTimeField(auto_now_add=True)
class ArticleNotification():
article = models.ForeignKey(Article)
msg = models.CharField()
is_seen = models.BooleanField(default=False)
datetime = models.DateTimeField(auto_now_add=True)
If someone commented on article the owner will see notifications.
#transaction.atomic
def post_comment(request, article_id):
comment = Comment.objects.create(article_id=article_id, txt="Nice Article", user=request.user)
ArticleNotification.objects.create(article_id=article_id, msg=f"User {request.user} commented on your post")
Now to show the notifications I normally make a context processor:
# context_processor:
def notifcations(request):
notifs = Notfication.objects.filter(article__owner=request.user).order_by("-datetime")
return {"notifs":notifs}
In this way I can normally implement basic notification system with refresh.
Now in (drf + react) what will be the preferred way for this type of task.
Instead of context processor should I have to make an get api to list notifications
And call this api on every request from react frontend ?
Instead of context processor should I have to make an get api to list notifications
Yes. You can create DRF API view like this
serializers.py
class ArticleNotificationSerializer(ModelSerializer):
class Meta:
model = ArticleNotification
fields = ["id", "article", "msg", "is_seen", "datetime"]
views.py
class ArticleNotificationListView(ListAPIView):
serializer_class = ArticleNotificationSerializer
queryset = ArticleNotification.objects.all()
urls.py
path('notification', ArticleNotificationListView.as_view()),
And call this api on every request from react frontend ?
Yes. Also you can check for Notifications for every 10 seconds with setInterval and componentDidMount hook in your react component.
componentDidMount: function() {
this.countdown = setInterval(function() {
axios.get(
'/notifications/'
).then(r =>
this.setState({ notifications: r.data }); // Changing state
)
}, 10000);
},
For real-time notification, you need something like Django channels or you can set a get api from react which runs after every defined time (say 5 minutes) and would fetch the required notifications based on user.
In your case things in context processor would be in listapiview and later you can fetch all the list.

Whats the proper way to transmit updating data to a client via Django websockets?

Currently, I have an element that when clicked, sets up a global cooldown timer that effects all clients with the use of Django websockets. My issue is that while initially the websocket value is converted to state in my React client via componentDidMount, the websocket doesn't run again when its value changes in real time.
Heres how it works in detail.
The timer is updated via a django model, which I broadcast via my websocket to my React front-end with:
consumer.py
class TestConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connected", event)
await self.send({
"type":"websocket.accept",
})
#correct way to grab the value btw, just work on outputting it so its streaming
#database_sync_to_async
def get_timer_val():
val = Timer.objects.order_by('-pk')[0]
return val.time
await self.send({
"type": "websocket.send",
"text": json.dumps({
'timer':await get_timer_val(),
})
})
async def websocket_receive(self, event):
print("received", event)
async def websocket_disconnect(self, event):
print("disconnected", event)
This works initially, as my React client boots up and converts the value to state, with:
component.jsx
//handles connecting to the webclient
componentDidMount() {
client.onopen = () => {
console.log("WebSocket Client Connected");
};
client.onmessage = (message) => {
const myObj = JSON.parse(message.data);
console.log(myObj.timer);
this.setState({ timestamp: myObj.timer });
};
}
//handles submitting the new timer upon clicking on element
handleTimer = () => {
// handles making PUT request with updated cooldown timer upon submission,
const timestamp = moment().add(30, "minutes");
const curr_time = { time: timestamp };
axios
.put(URL, curr_time, {
auth: {
username: USR,
password: PWD,
},
})
.then((res) => {
console.log(res);
});
};
//button that prompts the PUT request
<button
type="submit"
onClick={(e) => {
this.handleTimer();
//unrelated submit function
this.handleSubmit(e);
}}
>
Button
</button>
However, when a user clicks the rigged element and the database model changes, the web socket value doesn't until I refresh the page. I think the issue is that I'm only sending the websocket data during connection, but I don't know how to keep that "connection" open so any changes automatically get sent to the client server. I've looked through tons of links to find what the best way to implement real time is, but most of them are either about socket.io or implementing a chat app. All I want to do is stream a django models value to the front-end in real time.
When you want to send updates triggered by some other code to the websocket connection, the channels part of django-channels comes into play. It works like this:
On connection, you add the websocket to some named group
When the value of Timer changes, you send the event (via the channels layer) with certain type to this group, from the code that triggered the changes.
Django-channels then invoke the method of the Consumer named after the type of the event for each websocket in the group
And finally, in this method, your code sends the message to the client
You need to configure then channels layer with the Redis. https://channels.readthedocs.io/en/stable/topics/channel_layers.html
Now, step by step. I'll omit irrelevant parts.
1
async def websocket_connect(self, event):
await self.send({
"type":"websocket.accept"
})
await self.channel_layer.group_add('timer_observers', self.channel_name)
2 Here I am sending the event inside model, but you can do this in the view, or via django signals, however you want it. Also I am not checking whether the value actually changed, and I am assuming there is only one instance of Timer in the DB.
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
class Timer(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
async_to_sync(get_channel_layer().send)(
'timer_observers', {"type": "timer.changed"}
)
3+4
I have extracted the time-sending code to reuse it
class TestConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connected", event)
await self.send({
"type": "websocket.accept",
})
await self.channel_layer.group_add('timer_observers', self.channel_name)
await self.send_current_timer()
async def timer_changed(self, event):
await self.send_current_timer()
async def send_current_timer(self):
#database_sync_to_async
def get_timer_val():
val = Timer.objects.order_by('-pk')[0]
return val.time
await self.send({
"type": "websocket.send",
"text": json.dumps({
'timer': await get_timer_val(),
})
})
The idea here is that you handle internal events generated by your application the same way as external events from the client, i.e. websocket.connect -> async def websocket_connect. So the channels layer kinda "sends" you a "websocket message", and you respond (but to the actual client).
I hope that helps to understand the concepts. Probably what you are doing is overkill, but I assume that's just a learning exercise =)
I am not 100% sure this will work, so don't hesitate to ask additional questions.

DRF APIView returns empty data object when using apisauce but not on browser view

I am trying to query a model to check if an object exists or not using the following APIview function. Unfortunately i am not able to receive any response object in react native apisauce get function as shown below
apisauce function
const checkIfUserExists = (email) =>
client.get("/account/user-exists/?email=" + email);
APIView class
class UserExistsView(APIView):
"""Checks if user exists or not"""
def get(self, request, *args, **kwargs):
username = self.request.GET['email']
try:
user = models.UserProfile.objects.get(email=username)
except models.UserProfile.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
Results from the browser
What am i doing wrong here?
Print result on shell <Response status_code=200, "text/html; charset=utf-8">
You can do it simplier:
class UserExistsView(APIView):
"""Checks if user exists or not"""
def get(self, request, *args, **kwargs):
email = self.request.GET['email']
user_exists = models.UserProfile.objects.filter(email=email).exists()
return Response(
data={'message': user_exists}
)
If you create a QuerySet and then call .exists(), Django will call the SQL function checking only if the object is in the database, but it will not query it, which is much faster and desired in your call from JS.
Assuming you have a class base component, You could modify your function like below, track the user existence in state. I don't see any errors in your server-side code. but in your javascript code you didn't handle the promise.
checkUserExists= (email) => {
client.get("/account/user-exists/?email=" + email)
.then(response=>{
this.setState({isUserExists : response.data.messgae})
)
}

ReactJS form submit this.props.history.push('/downloads') not linking through to the new page

I am trying to Push to a new page once a user has filled out a form using this.props.history.push inside the function below.
handleSubmit = async event => {
event.preventDefault()
try {
const res = await newEnquiry(this.state.formData)
this.props.history.push('/downloads')
console.log(res)
} catch (err) {
console.log(err.response.data)
}
}
The ReactJS form is working fine on the /contacts page, and submits information to my Django back-end so I know it's working OK, however I cannot get the redirect to work and it's giving me this error message.
<form onSubmit={this.handleSubmit}> is inside my form tag and that's working fine so I am pretty sure it's not a problem with my form.
Api.js
const baseUrl = '/api'
export const newEnquiry = formData => {
return axios.post(`${baseUrl}/enquiries/`, formData)
}
Views.py
class EnquiryListView(APIView):
def get(self, _request):
enquiries = Enquiries.objects.all()
serialized_enquiries = EnquirySerializer(enquiries, many=True)
return Response(serialized_enquiries.data, status=status.HTTP_200_OK)
def post(self, request):
created_enquiry = EnquirySerializer(data=request.data)
if created_enquiry.is_valid():
created_enquiry.save()
return Response(created_enquiry.data, status=status.HTTP_201_CREATED)
return Response(created_enquiry.errors)
serializers.py
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiries
fields = '__all__'
In your case of this problem, when the error fires off, it is unable to read the err.response.data property. If there was no error, it would redirect you. In your Django app, check what the error handler is suppose to return.
Quick review of try/catch.
try {
// if everything passes, run this block
} catch (err) {
// if something goes wrong, run this block
}
In this case, be sure to check what your full error is. It might be a 404 or something totally unexpected.

AngularJS + django REST api

I'm attempting to build an api with DRF.
Client is a cordova app backed with AngularJS.
When I try to post some user object using $resource I'm getting a 403 forbidden response from django.
Below is some code which I think is relevant for the issue:
The API Call:
$rootScope.user =
User.get({id: response.id}).$promise.then(angular.noop, function (e) {
if (e.status == 404) { //If not found, register the user.
$rootScope.user = new User();
Object.keys(response).forEach(function (key) {
$rootScope.user[key] = response[key];
});
$rootScope.user.$save(); //Fails here! 403.
}
else
console.log(JSON.stringify(e.msg));
});
The User factory:
.factory('User', function ($resource, serverConstants) {
return $resource(serverConstants.serverUrl + '/users/:id');
})
django view:
# Users
class UserSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.CharField(max_length=100,required=True)
email = serializers.EmailField(required=False,allow_blank=True)
joined = serializers.DateField(required=False,default=datetime.date.today)
class Meta:
model = models.User
fields = ('joined', 'id', 'email')
def get_validation_exclusions(self):
exclusions = super(UserSerializer, self).get_validation_exclusions()
return exclusions + ['owner']
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
PS: I've configured angular to use CSRF cookie and django to allow CORS
Thanks in advance!
Your /user/:id endpoint requires authenticated requests.
You need to authenticate your client's requests using one of the methods specified on the previous link.
Given your app runs in a WebView and then has a builtin cookies handling, SessionAuthentication is the more straightforward to implement.
If you want the endpoint to not require authentication, you can set its permission_classes attribute like so:
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
permission_classes = (AllowAny, )
I guess with DRF you mean the django-rest-framework.
If yes, have a look here:
http://www.django-rest-framework.org/api-guide/authentication/
You can make the view public but using AllowAny.
from rest_framework.permissions import AllowAny
from rest_framework import generics
restapi_permission_classes = (AllowAny,)
class MyListView(generics.ListCreateAPIView):
serializer_class = MyObjectSerializer
permission_classes = restapi_permission_classes
queryset = MyObject.objects.all()
However I'd recommend you to use proper authentication once you are done with testing. I've been using the token authentication.
Have a look at this post for more details:
Django Rest Framework Token Authentication

Resources