Closed Voidmaster closed 10 years ago
Sure this is easy (you need to have access to the user in the router, and this can be achieved by using swampdragon-auth. pip install SwampDragon-auth will do the trick
. For usage see: https://github.com/jonashagstedt/swampdragon-auth).
Let's assume the notification model looks like this (note that I made the Notification model a SelfPublishModel
):
class Notification(SelfPublishModel, models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
user = models.ForeignKey(User)
serializer_class = NotificationSerializer
Create a router for notifications
class NotificationRouter(ModelRouter):
serializer_class = NotificationSerializer
model = Notification
route_name = 'notifications'
def get_query_set(self, **kwargs):
return self.model.objects.filter(user=self.connection.user)
def get_object(self, **kwargs):
return self.model.objects.get(user=self.connection.user, pk=kwargs['pk'])
def get_subscription_contexts(self, **kwargs):
return {'user__id': self.connection.user}
Let me know if anything doesn't make sense.
Thanks you for your rapid response.
I've tried to do my NotificationController but I've failed. I don't really understand how routers and controllers work. I've managed to do the tutorial and read the documentation but I can't get the big picture. Maybe because I'm a noob in angular and never used twister+redis.
1) Can you give me an example of the notification controller? 2) What does the get_subscription_contexts method do? It's not in the ModelRouter documentation.
If you think this project can be really useful in production, I would love to humbly help you!.
I think the swampdragon webpage could be more attractive and friendly, and documentation larger and with more examples. For those who don't know how the background(redis, tornado) works and have poor experience with angular and routers this can feel foreign(especially if you are from Argentina and have to learn all this in other lenguage)
If you want, you can help me out understanding how this works and I could help you test, improve and promote this project!
Thanks you again!
Here comes a long response:
I'm guessing you mean Twisted? (SwampDragon is not using Twisted, it's using Tornado and Django)
The purpose of the routers is to direct messages to and from the client (the client being the web browser in most scenarios).
If you think about this in terms of Django views and urls:
Let's pretend we have a view:
class FooView(DetailView):
model = FooModel
def get_object(self, **kwargs):
return self.model.objects.get(pk=kwargs['pk'])
and we add this view to urls.py
url(r'^foo/', FooView.as_view(), name='foo'),
You would send a request to http://localhost:8000/foo/
In Django, the url "foo" would be looked up in the urls.py
and the corresponding view would receive a request object.
In SwampDragon, you would have the following setup:
class FooRouter(ModelRouter):
model = FooModel
serializer_class = FooSerializer
route_name = 'foo'
def get_object(self, **kwargs):
return self.model.objects.get(pk=kwargs['pk'])
route_handler.register(FooRouter)
You can think of FooRouter
as the FooView
,
and route_handler.register(FooRouter)
similar to url(r'^foo/', FooView.as_view(), name='foo'),
With http requests you have a specific set of verbs (like GET, POST, PUT etc.), however in SwampDragon you create these verbs your self.
So instead of def post(self, request, **kwargs)
(in Django views) you would have
def create(self, **kwargs)
I'm using views and urls here because it's something we are all familiar with them from Django.
Controllers are AngularJS specific in this case (and you don't have to use AngularJS should you not want to). The base JS of SwampDragon is not framework specific (though there is an Angular service included) so it should in theory work with any JS framework out there.
If you look at http://swampdragon.net/documentation/javascript/ you can see more JavaScript documentation there (and there is only one section of all of them that is Angular specific).
I have opted to use AngularJS in most my applications because it simplifies things once you learn it, but it is absolutely not a requirement for SwampDragon.
With all that said, I will give an example of a NotificationController:
var NotificationControllers = angular.module('NotificationControllers', []);
NotificationControllers.controller('NotificationCtrl', ['$scope', '$dragon', function($scope, $dragon) {
$scope.channel = 'notification'; // this is your local channel (not the same as a channel on the server).
$scope.router = 'notifications'; // this is the route name
$scope.notifications = [];
// Once SwampDragon is connected and ready to
// receive and send data, the onReady function will be called
$dragon.onReady(function() {
// As we are ready to interact with the server now, we call
// getList on the 'notifications' router
$dragon.getList($scope.router).then(function(response) {
// Assuming everything went well, we will have
// a list of notifications belonging to the user.
// We assign these notifications to $scope.notifications so we can use them
// in the template.
$scope.notifications = response.data;
});
// We also subscribe to any new notifications
$dragon.subscribe($scope.router, $scope.channel).then(function(response) {
// The response from the server will include a map of the object.
// The DataMapper knows if it should update an existing object in an array, or add a new one
// or delete an existing one. It is a helper more than a requirement (but it does a lot of the work for you)
$scope.dataMapper = new DataMapper(response.data);
});
});
// Here we handle messages on a channel from the server
// (notifications are received on a channel we subscribed to).
$dragon.onChannelMessage(function(channels, message) {
// We check if the channels we received from the server contains
// our local channel (SwampDragon keeps track on the clients side
// which server channels are mapped to which client channels)
if (indexOf.call(channels, $scope.channel) > -1) {
// We tell the data mapper to handle the data mapping
// for us so we don't have to loop over all the notifications
// to check if the notification is already in the list, if it has been updated
// or removed.
$scope.$apply(function() {
this.dataMapper.mapData($scope.notifications, message);
});
}
});
}]);
(note: I wrote this code without testing it so it might contain typos)
In short: get_subscription_contexts
works a bit like (a basic version of) Django ORM filtering (the link outlines what works).
So if you have a model:
class Bar(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, null=True)
and a router:
class BarRouter(ModelRouter):
model = Bar
... # omitting all irrelevant code
def get_subscription_contexts(self, **kwargs):
return {name__contains='foo'}
And you run the following code:
Bar.objects.create(name='foo') # This would get published because the name contains 'foo'
Bar.objects.create(name='foobar') # this one as well
Bar.objects.create(name='bar') # but not this one, as the name doesn't contain foo
It certainly could be more attractive (but I'm fed up with every new product using Twitter Bootstrap scaffolding. SwampDragon.net is a lot faster than your standard bootstrap website).
I have added more documentation earlier today. If you see a specific feature that could do with more documentation please mention it and I'll look in to it.
One of the up-sides of SwampDragon is that you don't have to know much about Tornado as most of it is handled by SwampDragon, so you don't have to learn the workings of Tornado. You wouldn't necessarily even have to touch any of the Tornado code when working with SwampDragon. Saying all that, Tornado have a lot of good documentation for those who wants to know more about it.
I would love a nice sleek functional design for the website, along with a good logo.
This is a tricky question to answer as I am unsure about your specific production environment. I can however say that I have done some rudimentary testing by connecting several hundred simultaneous connections.
I think I had about 400 - 500 connection without any issues on a small server, all broadcasting messages to each other. The same server is also the web server, the database server and the Redis server.
I also ran a test against the chart demo, with 300 users posting data. Since all users are subscribed to the same channel, that means one user posting one message, that message is received by 300 users. So 300 users posting 300 messages, that is
I don't remember the exact number in milliseconds but I believe it was under a second.
If we do some naive maths and assumptions:
If a small and cheap server can easily handle 300 users at one point in time, and we pretend that each user spends on average 30 minutes on the site. If we also make the assumption that the server is used 24 hours a day. That means your server can handle 14,400 users a day.
Now this is clearly naive math full of poor assumptions, and just like any website, it depends on how much work the server does. If you have lots of heavy database queries then the site will of course have a perform worse.
On the other hand, you can easily split SwampDragon out on multiple servers. Just like you would load balance a website.
I wrote a blog post on deploying it in production (http://swampdragon.net/blog/deploying-swampdragon/)
Redis is crazy fast. It can handle millions of requests (see http://redis.io/topics/benchmarks) without breaking a sweat.
I've been meaning to write some text about contributions, but suggestions and contributions are welcome.
Hopefully this answered your questions. If anything is unclear just let me know.
Yes, I mean twisted!
Thanks you again Jonas. I wont tell you how much I appreciate it again. Instead, I will design a new website and logo as reward for your attention and detailed help.
I will take a look at your comment, documentation and try out the example.
One last question. This serializers replaces, for example, DjangoRestFramework serializers? or they may be compatible?
Regards from Argentina
The serializers wouldn't replace DjangoRestFramework's serializers. They are unfortunately not compatible. SwampDragons serializers do other things and works slightly differently (they provide object maps, which is not needed by DRF).
With SwampDragon you would make a custom serializer function by just adding serialize_<name>
in front of it:
def serialize_name(obj, serializer):
return '{} {}'.format(obj.first_name, obj.last_name)
This would not work in DRF. In DRF you would instead do:
name = serializers.SerializerMethodField('get_name')
gef get_name(self, obj):
return '{} {}'.format(obj.first_name, obj.last_name)
However, they are not mutually exclusive, so you could use both DRF and SD in the same project. I drew a lot of inspiration from DRF when I designed SD (Tom Christie and the guys behind DRF have done an amazing job!)
Hi, Jonas. Thank you so much for doing this. It has been really useful and simple to use!
I was wondering if you can explain me how can I use this to send notifications via signals. For example:
I have a todo list. I want to asign a taks to an user and then I want the system notify that user "User.name has assigned you Task.name"
It's there a way to do this real-time?
Thanks you again and forgive my English! (I'm from Argentina)