diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3b7c2ad --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python +python: +- '2.7' +services: +- mysql +before_script: +- mysql -e 'CREATE DATABASE social_test;' +install: pip install -r requirements.txt +script: +- python manage.py migrate --noinput +sudo: false +after_success: +- openssl aes-256-cbc -K $encrypted_aa5263036386_key -iv $encrypted_aa5263036386_iv + -in .travis/ssh_key.enc -out .travis/deploy -d +- echo "madoka.whs.in.th,103.246.18.42 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN717hGRfIpN9psGG9q8VXuyVE6w8H1Fa0BqkHOBGJFgAq2uV0/bdYNOpvG27ngLDOmOmNLpdlIYQiI56E+qUPUBxLH3fvhVQVKItP5MRlc97an98Ozg+bOl3SjdUWgFeHIRHwu6UllZUL8U6pjJS/GZsumzwwwTOnLRutYWegxeHjW2DU5Q2BkCU5z/3b+7gFu4s9gRFZ57En/GG2aZYrKBGgoUvjEAOL3B+/nCm1ZTlV2IdKA9gpF/7SyZIozKHXXUA/Xw27aOxebFL2VHO7YtgvkNoiJ5uc6ZQuRB9VPmNKBSYH5fwSICtSHTGlCKLOUd91SBG8JRm/fXES24jn" + > $HOME/.ssh/known_hosts +- chmod 600 .travis/deploy $HOME/.ssh/known_hosts +- test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && ssh -i + .travis/deploy vu2003@madoka.whs.in.th social/deploy.sh \ No newline at end of file diff --git a/.travis/ssh_key.enc b/.travis/ssh_key.enc new file mode 100644 index 0000000..652ae53 --- /dev/null +++ b/.travis/ssh_key.enc @@ -0,0 +1 @@ +$s@c1W6 `EM.kx!-Sc 3 :y襘U"eQ-1lzGc}x, )ډ0ZTS:&;+Q.@攎&:^0mZL#@ύdڋB0^tƊţdl&xFG7MbU)W$k wi8&H;> \ No newline at end of file diff --git a/group/serializers.py b/group/serializers.py index 0a5a411..3a365a0 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -17,5 +17,6 @@ class Meta: class GroupSerializer(serializers.ModelSerializer): class Meta: + app_label = "social_group" model = Group fields = ('name', 'description', 'short_description', 'activities', 'type') \ No newline at end of file diff --git a/group/urls.py b/group/urls.py index 4f3d773..b9463af 100644 --- a/group/urls.py +++ b/group/urls.py @@ -11,4 +11,6 @@ url(r'^all/$', views.GroupViewSet.as_view(), name='LookForGroup'), url(r'^(?P[0-9]+)/edit/$', views.EditInfo.as_view(), name='EditInfo'), url(r'^category/(?P[a-zA-Z]+)/$', views.GroupByCategory.as_view(), name='GroupByCategory'), + url(r'^(?P[0-9]+)/post/$', views.GroupPostView.as_view(), name='PostDetail'), + ] diff --git a/group/views.py b/group/views.py index e26f823..9f3c13c 100644 --- a/group/views.py +++ b/group/views.py @@ -5,6 +5,11 @@ from rest_framework.exceptions import NotAuthenticated from rest_framework.exceptions import NotFound from rest_framework.views import APIView +from django.contrib.contenttypes.models import ContentType +from newsfeed.models import Post +from newsfeed.models import Comment +from newsfeed.serializer import GroupPostSerializer +from newsfeed.serializer import CommentSerializer from models import * from serializers import * from django.http import Http404 @@ -45,18 +50,23 @@ def create(self, *args, **kwargs): return Response(GroupMemberSerializer(member).data) + class PendingMemberViewSet(ListCreateAPIView): serializer_class = GroupMemberSerializer + def get_queryset(self): this_group = Group.objects.get(id=int(self.kwargs['group_id'])) return GroupMember.objects.filter(group=this_group, role=0) + class AcceptedMemberViewSet(ListCreateAPIView): serializer_class = GroupMemberSerializer + def get_queryset(self): this_group = Group.objects.get(id=int(self.kwargs['group_id'])) return GroupMember.objects.filter(group=this_group, role=1) + class GroupViewSet(APIView): serializer_class = GroupSerializer @@ -75,6 +85,7 @@ def post(self, request, format=None): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class GroupViewDetail(APIView): serializer_class = GroupSerializer @@ -98,6 +109,7 @@ def post(self, request, group_id, format=None): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class MemberDetail(APIView): serializer_class = GroupMemberSerializer @@ -124,6 +136,7 @@ def get(self, request, group_id, pk, format=None): return Response(response.data) + class EditInfo(APIView): serializer_class = GroupSerializer @@ -146,4 +159,25 @@ class GroupByCategory(APIView): def get(self, request, cat, format=None): group = Group.objects.filter(category=cat) response = self.serializer_class(group, many=True) - return Response(response.data) \ No newline at end of file + return Response(response.data) + + +class GroupPostView(APIView): + serializer_class = GroupPostSerializer + group_model_id = 15 + + def get(self, request, group_id, format=None): + post = Post.objects.filter(target_id=group_id, target_type=self.group_model_id).order_by('-datetime') + response = self.serializer_class(post, many=True) + return Response(response.data) + + def post(self, request, group_id, format=None): + serializer = GroupPostSerializer(data=request.data) + + if serializer.is_valid(): + + if self.request.user.is_authenticated(): + serializer.save(user=User.objects.get(id=self.request.user.id), target_id=group_id, target_type=ContentType.objects.get(id=self.group_model_id)) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + diff --git a/newsfeed/models.py b/newsfeed/models.py index 48409e8..9fe1b2f 100644 --- a/newsfeed/models.py +++ b/newsfeed/models.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType +from django.contrib.humanize.templatetags.humanize import naturaltime class Post(models.Model): @@ -12,6 +13,9 @@ class Post(models.Model): target_id = models.PositiveIntegerField() target_object = GenericForeignKey('target_type', 'target_id') + def FORMAT(self): + return naturaltime(self.datetime) + def __unicode__(self): return "{}'s newsfeed (id={})".format(self.user.username, self.id) @@ -22,5 +26,8 @@ class Comment(models.Model): text = models.CharField(max_length=2000) datetime = models.DateTimeField(auto_now_add=True) + def FORMAT(self): + return naturaltime(self.datetime) + def __unicode__(self): return "{}'s comment (id={})".format(self.user.username, self.id) diff --git a/newsfeed/serializer.py b/newsfeed/serializer.py index 8a5f90c..6f15c9f 100644 --- a/newsfeed/serializer.py +++ b/newsfeed/serializer.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import User +from rest_framework import serializers from rest_framework.serializers import ModelSerializer from newsfeed.models import Post, Comment @@ -12,16 +13,32 @@ class Meta: class PostSerializer(ModelSerializer): user = UserSerializer(read_only=True) + datetime = serializers.ReadOnlyField(source='FORMAT') class Meta: model = Post fields = ('id', 'user', 'text', 'datetime', - 'target_type', 'target_id', 'target_object') + 'target_type', 'target_id') + #'target_type', 'target_id', 'target_object') -class CommentSerializer(ModelSerializer): +class GroupPostSerializer(ModelSerializer): + group_model_id = 15 user = UserSerializer(read_only=True) + target_type = serializers.HiddenField(default=group_model_id) + target_id = serializers.HiddenField(default=0) + datetime = serializers.ReadOnlyField(source='FORMAT') + + class Meta: + model = Post + fields = ('id', 'user', 'text', 'datetime', + 'target_type', 'target_id') + +class CommentSerializer(ModelSerializer): + user = UserSerializer(read_only=True) + datetime = serializers.ReadOnlyField(source='FORMAT') + class Meta: model = Comment fields = ('id', 'post', 'user', 'text', 'datetime') diff --git a/newsfeed/views.py b/newsfeed/views.py index fbfefc6..a027bbf 100644 --- a/newsfeed/views.py +++ b/newsfeed/views.py @@ -14,11 +14,11 @@ class PostViewList(APIView): serializer_class = PostSerializer def get(self, request, id=None, format=None): - post = Post.objects.all() + post = Post.objects.order_by('-datetime') response = self.serializer_class(post, many=True) return Response(response.data) - + def post(self, request, format=None): serializer = PostSerializer(data=request.data) diff --git a/ui/static/app/group.js b/ui/static/app/group.js index 05fe1aa..b68c59d 100644 --- a/ui/static/app/group.js +++ b/ui/static/app/group.js @@ -19,6 +19,74 @@ app.controller('GroupController', function($scope, $stateParams, Restangular, $h }); }); + +app.controller('GroupFeedController', function($scope, $http, $location){ + $scope.allowPost = true; + $scope.newsfeed = null; + $scope.nftext =""; + var groupID = $location.path().split('/')[2]; + $http.get('/api/group/'+groupID+'/post').success(function(data){ + $scope.newsfeed = data; + }); + + $scope.postStatus = function() { + postData = { + text : $scope.nftext, + }; + + if (postData.text.length > 0) { + $http.post('/api/group/'+groupID+'/post/', postData).then(function(response){ + console.log(response); + $scope.nftext=""; + $scope.newsfeed.unshift(response.data); + }, function(xhr){ + alert(xhr.data); + console.log(xhr.data); + }); + } + }; + +}); + + + +app.controller('GroupCommentController', function($rootScope, $scope, $http){ + + var loadCommentsByPostId = function(postID) { + $http.get('/api/newsfeed/post/'+postID+'/comment/').success(function(commentsData){ + $scope.comments = commentsData; + }); + }; + + loadCommentsByPostId($scope.data.id); + $scope.comments = null; + + $scope.commentPost = function(postData) { + commentData = { + text : $scope.comment, + post : postData.id, + datetime : 'Just now', + user : { + username : $rootScope.user, + }, + }; + + if (commentData.text.length > 0) { + $scope.comments.push(commentData); + $scope.comment = ""; + $http.post('/api/newsfeed/comment/', commentData).then(function(response){ + console.log(response); + loadCommentsByPostId($scope.data.id); + }, function(xhr){ + alert(xhr.data); + console.log(xhr.data); + }); + } + }; + +}); + + app.controller('GroupInfoController', function($scope, $http, $location){ var groupID = $location.path().split('/')[2]; $http.get('/api/group/'+groupID).success(function(data){ @@ -54,6 +122,7 @@ app.controller('GroupManageController', function($scope, $http, $location){ $scope.acceptMember = acceptMember; $scope.denyMember = denyMember; + $http.get('/api/group/'+groupID).then(function(data){ $scope.group = data.data; }); @@ -73,4 +142,5 @@ app.controller('GroupCategoryController', function($scope, $http, $stateParams){ }); -})(); \ No newline at end of file +})(); + diff --git a/ui/static/app/main.js b/ui/static/app/main.js index c9e95a9..fae4992 100644 --- a/ui/static/app/main.js +++ b/ui/static/app/main.js @@ -21,7 +21,16 @@ app.config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('root', { templateUrl: 'templates/root.html', - controller: 'MainAuthController' + controller: 'MainController', + resolve: { + user: function(Restangular){ + return Restangular.one('auth/check').get().then(function(user){ + return user; + }, function(error){ + return null; + }); + } + }, }) .state('root.newsfeed', { url: '/', @@ -39,6 +48,11 @@ app.config(function($stateProvider, $urlRouterProvider) { templateUrl: 'templates/groupinfo.html', controller: 'GroupInfoController' }) + .state('root.group.feed', { + url: '/feed', + templateUrl: 'templates/groupfeed.html', + controller: 'GroupFeedController' + }) .state('root.group.manage', { url: '/manage', templateUrl: 'templates/groupmanage.html', @@ -53,6 +67,19 @@ app.config(function($stateProvider, $urlRouterProvider) { templateUrl: 'templates/groupbrowser_cat.html', controller: 'GroupCategoryController' }) + .state('root.creategroup', { + url: '/groups/create', + templateUrl: 'templates/groupcreate.html', + }) + .state('root.user', { + url: '/{user:int}', + abstract: true, + templateUrl: 'templates/user.html' + }) + .state('root.user.timeline', { + url: '/', + templateUrl: 'templates/usertimeline.html' + }) .state('login', { url: '/login', templateUrl: 'templates/login.html', @@ -60,13 +87,11 @@ app.config(function($stateProvider, $urlRouterProvider) { }); }); -app.controller('MainAuthController', function($rootScope, Restangular, $state){ - $rootScope.user = null; - Restangular.one('auth/check').get().then(function(user){ - $rootScope.user = user; - }, function(){ - $state.go('login'); - }); +app.controller('MainController', function($rootScope, user){ + $rootScope.user = user; +}); +app.controller('NotificationController', function($rootScope){ + $rootScope.notificationCount = Math.floor(Math.random() * 20); }); -})(); \ No newline at end of file +})(); diff --git a/ui/static/app/newsfeed.js b/ui/static/app/newsfeed.js index 92fcfdd..d0f138e 100644 --- a/ui/static/app/newsfeed.js +++ b/ui/static/app/newsfeed.js @@ -24,7 +24,7 @@ app.controller('NewsfeedController', function($scope, $http){ $scope.nftext =""; $http.get('/api/newsfeed/post').success(function(data){ $scope.newsfeed = data; - }) + }); $scope.postStatus = function() { postData = { @@ -34,10 +34,10 @@ app.controller('NewsfeedController', function($scope, $http){ }; if (postData.text.length > 0) { - $http.post('/api/newsfeed/post/', postData).then(function(){ - alert("Post Successful!"); - console.log("Post Successful!"); - location.reload(); + $http.post('/api/newsfeed/post/', postData).then(function(response){ + console.log(response); + $scope.nftext=""; + $scope.newsfeed.unshift(response.data); }, function(xhr){ alert(xhr.data); console.log(xhr.data); @@ -49,25 +49,33 @@ app.controller('NewsfeedController', function($scope, $http){ -app.controller('CommentController', function($scope, $http){ +app.controller('CommentController', function($rootScope, $scope, $http){ + + var loadCommentsByPostId = function(postID) { + $http.get('/api/newsfeed/post/'+postID+'/comment/').success(function(commentsData){ + $scope.comments = commentsData; + }); + }; + + loadCommentsByPostId($scope.data.id); $scope.comments = null; - $http.get('/api/newsfeed/post/'+$scope.data.id+'/comment/').success(function(commentsData){ - $scope.comments = commentsData; - }) - - $scope.commentPost = function(post_data) { - console.log(post_data); - console.log($scope.comment); - data = { + + $scope.commentPost = function(postData) { + commentData = { text : $scope.comment, - post : post_data.id, + post : postData.id, + datetime : 'Just now', + user : { + username : $rootScope.user, + }, }; - if (data.text.length > 0) { - $http.post('/api/newsfeed/comment/', data).then(function(){ - alert("Comment Successful!"); - console.log("Comment Successful!"); - location.reload(); + if (commentData.text.length > 0) { + $scope.comments.push(commentData); + $scope.comment = ""; + $http.post('/api/newsfeed/comment/', commentData).then(function(response){ + console.log(response); + loadCommentsByPostId($scope.data.id); }, function(xhr){ alert(xhr.data); console.log(xhr.data); diff --git a/ui/static/assets/css/app.css b/ui/static/assets/css/app.css index a2b8f68..1265c0c 100644 --- a/ui/static/assets/css/app.css +++ b/ui/static/assets/css/app.css @@ -193,4 +193,54 @@ html,body,#root{ .groupblock .title{ margin-top: 0; margin-bottom: 10px; +} + +/* notification */ +.notibox{ + position: absolute; + top: 56px; + right: 40px; + border: #aaa solid 1px; + border-top: none; + box-shadow: rgba(100,100,100, 0.4) 0px 3px 3px; + width: 300px; + height: 400px; + z-index: 400; + background: white; +} +.notibox .header{ + background: #BFD74E; + font-size: 13pt; + padding: 4px 10px; + color: #22733D; + height: 36px; + line-height: 28px; +} +.notibox .body{ + height: 364px; + overflow: auto; +} +.notibox .item-outer{ + padding: 6px; +} +.notibox .item{ + margin: 0 10px; + border-bottom: #aaa solid 1px; + display: block; + cursor: pointer; +} + +/* user page */ +.cover .avatar{ + position: absolute; + bottom: -60px; + margin: 0; + width: 140px; + height: 140px; +} +.userpage .cover h3{ + margin-left: 160px; +} +.userpage .cover{ + margin-bottom: 80px; } \ No newline at end of file diff --git a/ui/static/templates/group.html b/ui/static/templates/group.html index 4440ded..485b621 100644 --- a/ui/static/templates/group.html +++ b/ui/static/templates/group.html @@ -9,8 +9,8 @@

{{group.name}}

-
\ No newline at end of file +
diff --git a/ui/static/templates/groupcreate.html b/ui/static/templates/groupcreate.html new file mode 100644 index 0000000..8e8a9c4 --- /dev/null +++ b/ui/static/templates/groupcreate.html @@ -0,0 +1,36 @@ +
+
+

Group name

+ +
+
+

Group type

+
+ +
+
+ +
+
+
+

Group privacy

+
+ +
+
+ +
+
+ +
+
+
+

What's this group about

+ +
+ +
\ No newline at end of file diff --git a/ui/static/templates/groupfeed.html b/ui/static/templates/groupfeed.html new file mode 100644 index 0000000..9ebc0ae --- /dev/null +++ b/ui/static/templates/groupfeed.html @@ -0,0 +1,50 @@ +
+ + +
+
+
+
+
+
+ +
+
+ {{data.user.username}} + +
+
+
+ {{data.text}} +
+
+ + +
+ +
+
diff --git a/ui/static/templates/groupmanage.html b/ui/static/templates/groupmanage.html index 0d49658..93848a4 100644 --- a/ui/static/templates/groupmanage.html +++ b/ui/static/templates/groupmanage.html @@ -36,7 +36,9 @@

What's this group about

+ +
diff --git a/ui/static/templates/notification.html b/ui/static/templates/notification.html new file mode 100644 index 0000000..41e919d --- /dev/null +++ b/ui/static/templates/notification.html @@ -0,0 +1,41 @@ +
+ Notifications +
+
+
+
+
+ +
+
+ Item type 1 + +
+
+
+
+
+
+
+ +
+
+ Somchai posted in Test event "Item type 2" + +
+
+
+
+
+
+
+ +
+
+ Somchai posted in Test group "Item type {{x}}" + +
+
+
+
+
\ No newline at end of file diff --git a/ui/static/templates/root.html b/ui/static/templates/root.html index 3f4d1f5..7457f7e 100644 --- a/ui/static/templates/root.html +++ b/ui/static/templates/root.html @@ -19,9 +19,9 @@
+