This commit is contained in:
esp 2015-08-11 19:43:47 +08:00
commit 72dbe24fd2
18 changed files with 646 additions and 18 deletions

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('group', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='group',
name='name',
field=models.CharField(unique=True, max_length=30),
),
migrations.AlterField(
model_name='joingrouprequest',
name='group',
field=models.ForeignKey(to='group.Group'),
),
migrations.AlterUniqueTogether(
name='usergrouprelation',
unique_together=set([('group', 'user')]),
),
]

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('group', '0002_auto_20150811_1456'),
]
operations = [
migrations.AlterField(
model_name='group',
name='join_group_setting',
field=models.IntegerField(default=1),
),
]

View File

@ -10,7 +10,7 @@ class Group(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
admin = models.ForeignKey(User, related_name="my_groups")
# 0是公开 1是需要申请后加入 2是不允许任何人加入
join_group_setting = models.IntegerField()
join_group_setting = models.IntegerField(default=1)
members = models.ManyToManyField(User, through="UserGroupRelation")
# 解散小组后这一项改为False
visible = models.BooleanField(default=True)

View File

@ -1,8 +1,9 @@
# coding=utf-8
from rest_framework import serializers
from account.models import User
from account.serializers import UserSerializer
from .models import Group, UserGroupRelation
from .models import Group, UserGroupRelation, JoinGroupRequest
class CreateGroupSerializer(serializers.Serializer):
@ -18,25 +19,58 @@ class EditGroupSerializer(serializers.Serializer):
join_group_setting = serializers.IntegerField()
class JoinGroupRequestSerializer(serializers.Serializer):
class CreateJoinGroupRequestSerializer(serializers.Serializer):
group_id = serializers.IntegerField()
message = serializers.CharField(max_length=30)
class JoinGroupRequestSerializer(serializers.ModelSerializer):
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ["id", "name"]
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["username"]
group = GroupSerializer()
user = UserSerializer()
class Meta:
model = JoinGroupRequest
class GroupSerializer(serializers.ModelSerializer):
members_number = serializers.SerializerMethodField("_get_group_members_number")
def _get_group_members_number(self, group):
return group.members.all().count()
class Meta:
model = Group
exclude = ["members"]
class GroupMemberSerializer(serializers.ModelSerializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "real_name"]
user = UserSerializer()
class Meta:
model = UserGroupRelation
exclude = ["id", "group"]
exclude = ["id"]
class EditGroupMemberSerializer(serializers.Serializer):
group_id = serializers.IntegerField()
members = serializers.ListField(child=serializers.IntegerField())
class PutJoinGroupRequestSerializer(serializers.Serializer):
request_id = serializers.IntegerField()
status = serializers.BooleanField()

View File

@ -10,8 +10,9 @@ from account.decorators import login_required
from .models import Group, JoinGroupRequest, UserGroupRelation
from .serializers import (CreateGroupSerializer, EditGroupSerializer,
JoinGroupRequestSerializer, GroupSerializer,
GroupMemberSerializer, EditGroupMemberSerializer)
CreateJoinGroupRequestSerializer, GroupSerializer,
GroupMemberSerializer, EditGroupMemberSerializer,
JoinGroupRequestSerializer, PutJoinGroupRequestSerializer)
class GroupAPIViewBase(object):
@ -101,6 +102,8 @@ class GroupAdminAPIView(APIView, GroupAPIViewBase):
return error_response(u"小组不存在")
else:
groups = self.get_groups(request)
if request.GET.get("keyword", None):
groups = groups.filter(name__contains=request.GET["keyword"])
return paginate(request, groups, GroupSerializer)
@ -149,14 +152,14 @@ def join_group(user, group):
class JoinGroupAPIView(APIView):
@login_required
# @login_required
def post(self, request):
"""
加入某个小组的api
---
request_serializer: JoinGroupRequestSerializer
request_serializer: CreateJoinGroupRequestSerializer
"""
serializer = JoinGroupRequestSerializer(data=request.data)
serializer = CreateJoinGroupRequestSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
@ -169,6 +172,10 @@ class JoinGroupAPIView(APIView):
else:
return error_response(u"你已经是小组成员了")
elif group.join_group_setting == 1:
try:
JoinGroupRequest.objects.get(user=request.user, group=group, status=False)
return error_response(u"你已经提交过申请了,请等待审核")
except JoinGroupRequest.DoesNotExist:
JoinGroupRequest.objects.create(user=request.user, group=group, message=data["message"])
return success_response(u"申请提交成功,请等待审核")
elif group.join_group_setting == 2:
@ -188,3 +195,43 @@ class JoinGroupAPIView(APIView):
# 搜索包含这个关键词的 没有解散的 而且允许加入的小组
groups = Group.objects.filter(name__contains=keyword, visible=True, join_group_setting__lte=2)
return paginate(request, groups, GroupSerializer)
class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
def get(self, request):
"""
返回管理的群的加群请求
---
response_serializer: JoinGroupRequestSerializer
"""
requests = JoinGroupRequest.objects.filter(group=Group.objects.filter(admin=request.user, visible=True),
status=False)
return paginate(request, requests, JoinGroupRequestSerializer)
def put(self, request):
"""
同意或者拒绝加入小组请求
---
request_serializer: PutJoinGroupRequestSerializer
"""
serializer = PutJoinGroupRequestSerializer(data=request.data)
if serializer.is_valid():
data = serializer.data
try:
join_request = JoinGroupRequest.objects.get(id=data["request_id"], group__admin=request.user, status=False)
except JoinGroupRequest.DoesNotExist:
return error_response(u"小组不存在")
join_request.status = True
join_request.save()
if data["status"]:
if join_group(join_request.user, join_request.group):
return success_response(u"加入成功")
else:
return error_response(u"加入失败,已经在本小组内")
else:
return success_response(u"已拒绝")
else:
return serializer_invalid_response(serializer)

View File

@ -3,10 +3,21 @@ from django.shortcuts import render
from django.http import HttpResponse
from account.models import User
from group.models import Group, UserGroupRelation, JoinGroupRequest
def install(request):
user = User.objects.create(username="root", admin_type=2)
for i in range(10):
user = User.objects.create(username="root" + str(i), admin_type=2, real_name="real_name", email="11111@qq.com")
user.set_password("root")
user.save()
for i in range(10):
group = Group.objects.create(name="group" + str(i),
description="description",
admin=User.objects.get(username="root0"))
for i in range(7):
UserGroupRelation.objects.create(user=User.objects.get(username="root" + str(i)), group=group)
for i in range(7, 10):
JoinGroupRequest.objects.create(user=User.objects.get(username="root" + str(i)),
group=group, message=u"你好啊")
return HttpResponse("success")

View File

@ -7,7 +7,9 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA
UserChangePasswordAPIView, EmailCheckAPIView,
UserAPIView, UserAdminAPIView)
from announcement.views import AnnouncementAPIView, AnnouncementAdminAPIView
from group.views import GroupAdminAPIView, GroupMemberAdminAPIView, JoinGroupAPIView
from group.views import GroupAdminAPIView, GroupMemberAdminAPIView, JoinGroupAPIView, JoinGroupRequestAdminAPIView
from admin.views import AdminTemplateView
from problem.views import ProblemAdminAPIView
@ -46,4 +48,7 @@ urlpatterns = [
url(r'^api/admin/tag/$', ProblemTagAdminAPIView.as_view(), name="problem_tag_admin_api"),
url(r'^problem/(?P<problem_id>\d+)/my_solutions/', "problem.views.problem_my_solutions_list_page", name="problem_my_solutions_page"),
url(r'^my_solution/(?P<solution_id>\d+)/$', "problem.views.my_solution", name="my_solution_page"),
url(r'^api/admin/join_group_request/$', JoinGroupRequestAdminAPIView.as_view(), name="join_group_request_admin_api"),
]

View File

@ -24,11 +24,17 @@ define("admin", ["jquery", "avalon"], function ($, avalon) {
var vm = avalon.define({
$id: "admin",
template_url: "template/" + hash + ".html",
group_id: -1,
hide_loading: function () {
$("#loading-gif").hide();
}
});
vm.$watch("showGroupDetailPage", function(group_id){
vm.group_id = group_id;
vm.template_url = "template/group/group_detail.html";
});
avalon.scan();
li_active("#li-" + hash.replace("/", "-"));

View File

@ -0,0 +1,73 @@
require(["jquery", "avalon", "csrf", "bs_alert", "validation"], function ($, avalon, csrfHeader, bs_alert) {
avalon.vmodels.group = null;
// avalon:定义模式 group_list
avalon.ready(function () {
var vm = avalon.define({
$id: "group",
//通用变量
group_list: [], // 用户列表数据项
previous_page: 0, // 之前的页数
next_page: 0, // 之后的页数
page: 1, // 当前页数
page_count: 1, // 总页数
keyword: "",
getNext: function () {
if (!vm.next_page)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previous_page)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) { //上一页/下一页按钮启用禁用逻辑
if (btn) {
return vm.next_page ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previous_page ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
getGroupSettingString: function(setting){
return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting]
},
showGroupDetailPage: function(group_id){
vm.$fire("up!showGroupDetailPage", group_id);
}
});
avalon.scan();
getPageData(1); //用户列表初始化
//Ajax get数据
function getPageData(page) {
var url = "/api/admin/group/?paging=true&page=" + page + "&page_size=10";
if (vm.keyword != "")
url += "&keyword=" + vm.keyword;
$.ajax({
beforeSend: csrfHeader,
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.group_list = data.data.results;
vm.page_count = data.data.total_page;
vm.previous_page = data.data.previous_page;
vm.next_page = data.data.next_page;
vm.page = page;
}
else {
bs_alert(data.data);
}
}
});
}
});
});

View File

@ -0,0 +1,119 @@
require(["jquery", "avalon", "csrf", "bs_alert", "validation"], function ($, avalon, csrfHeader, bs_alert) {
avalon.vmodels.group_detail = null;
// avalon:定义模式 group_list
avalon.ready(function () {
var vm = avalon.define({
$id: "group_detail",
//通用变量
member_list: [],
previous_page: 0,
next_page: 0,
page: 1,
page_count: 1,
name: "",
description: "",
join_group_setting: {0: false, 1: false, 2: false},
checked_setting: 0,
getSettingChecked: function (setting) {
return setting == vm.join_group_setting;
},
updateGroupInfo: function () {
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/group/",
method: "put",
data: {group_id: avalon.vmodels.admin.group_id, name: vm.name,
description: vm.description, join_group_setting: vm.join_group_setting},
dataType: "json",
success: function (data) {
if (!data.code) {
bs_alert("修改成功");
}
else {
bs_alert(data.data);
}
}
})
},
getNext: function () {
if (!vm.next_page)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previous_page)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) {
if (btn) {
return vm.next_page ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previous_page ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
removeMember: function (relation) {
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/group_member/",
method: "put",
data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}),
contentType: "application/json",
success: function (data) {
vm.member_list.remove(relation);
bs_alert(data.data);
}
})
}
});
avalon.scan();
getPageData(1);
function getPageData(page) {
var url = "/api/admin/group_member/?paging=true&page=" + page +
"&page_size=10&group_id=" + avalon.vmodels.admin.group_id;
$.ajax({
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.member_list = data.data.results;
vm.page_count = data.data.total_page;
vm.previous_page = data.data.previous_page;
vm.next_page = data.data.next_page;
vm.page = page;
}
else {
bs_alert(data.data);
}
}
});
}
$.ajax({
url: "/api/admin/group/?group_id=" + avalon.vmodels.admin.group_id,
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
vm.name = data.data.name;
vm.description = data.data.description;
vm.join_group_setting[data.data.join_group_setting] = true;
}
else {
bs_alert(data.data);
}
}
})
});
});

View File

@ -0,0 +1,148 @@
require(["jquery", "avalon", "csrf", "bs_alert", "validation"], function ($, avalon, csrfHeader, bs_alert) {
avalon.vmodels.request_list = null;
// avalon:定义模式 group_list
avalon.ready(function () {
var vm = avalon.define({
$id: "request_list",
//通用变量
request_list: [], // 列表数据项
previous_page: 0, // 之前的页数
next_page: 0, // 之后的页数
page: 1, // 当前页数
page_count: 1, // 总页数
getNext: function () {
if (!vm.next_page)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previous_page)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) { //上一页/下一页按钮启用禁用逻辑
if (btn) {
return vm.next_page ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previous_page ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
getPageData(page_index);
},
processRequest: function(request_id, status){
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/join_group_request/",
method: "put",
data: {request_id: request_id, status: status},
success: function(data){
bs_alert(data.data);
}
})
}
});
avalon.scan();
getPageData(1);
//Ajax get数据
function getPageData(page) {
var url = "/api/admin/join_group_request/?paging=true&page=" + page + "&page_size=10";
$.ajax({
beforeSend: csrfHeader,
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.request_list = data.data.results;
vm.page_count = data.data.total_page;
vm.previous_page = data.data.previous_page;
vm.next_page = data.data.next_page;
vm.page = page;
}
else {
bs_alert(data.data);
}
}
});
}
/*$("#edit_user-form")
.formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
},
stringLength: {
min: 3,
max: 30,
message: '用户名长度必须在3到30位之间'
}
}
},
real_name: {
validators: {
notEmpty: {
message: "请填写真实姓名"
}
}
},
email: {
validators: {
notEmpty: {
message: "请填写电子邮箱邮箱地址"
},
emailAddress: {
message: "请填写有效的邮箱地址"
}
}
},
password: {
validators: {
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var data = {
username: vm.username,
real_name: vm.real_name,
email: vm.email,
id: vm.id,
admin_type: vm.admin_type
};
if ($("#password").val() !== "")
data.password = $("#password").val();
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/user/",
data: data,
dataType: "json",
method: "put",
success: function (data) {
if (!data.code) {
bs_alert("提交成功!");
getPageData(1);
$("#password").val("");
} else {
bs_alert(data.data);
}
}
})
});*/
});
});

View File

@ -104,6 +104,13 @@
<li class="list-group-item" id="li-user-user_group">
<a href="#user/user_group">用户分组</a>
</li>
<li class="list-group-header">小组管理</li>
<li class="list-group-item" id="li-group-group">
<a href="#group/group">小组列表</a>
</li>
<li class="list-group-item" id="li-group-join_group_request_list">
<a href="#group/join_group_request_list">加入小组请求</a>
</li>
</ul>
</div>
<!-- admin left end -->

View File

View File

@ -0,0 +1,43 @@
<div ms-controller="group" class="col-md-9">
<h1>小组管理</h1>
<div class="text-right">
<form class="form-inline" onsubmit="return false;">
<div class="form-group-sm">
<label>搜索</label>
<input class="form-control" placeholder="请输入关键词" ms-duplex="keyword">
<input type="submit" value="搜索" class="btn btn-primary" ms-click="getPage(1)">
</div>
</form>
<br>
</div>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>名称</th>
<th>创建时间</th>
<th>人数</th>
<th>设置</th>
<th></th>
</tr>
<tr ms-repeat="group_list">
<td>{{el.id}}</td>
<td>{{el.name}}</td>
<td>{{el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.members_number }}</td>
<td>{{ getGroupSettingString(el.join_group_setting) }}</td>
<td>
<button class="btn-sm btn-info" ms-click="showGroupDetailPage(el.id)">详情</button>
</td>
</tr>
</table>
<div class="text-right">
页数:{{page}}/{{page_count}}&nbsp;&nbsp;
<botton ms-attr-class="getBtnClass(0)" ms-click="getPrevious">上一页</botton>
<botton ms-attr-class="getBtnClass(1)" ms-click="getNext">下一页</botton>
</div>
</div>
<script src="/static/js/app/admin/group/group.js"></script>

View File

@ -0,0 +1,58 @@
<div ms-controller="group_detail" class="col-md-9">
<h1>小组成员管理</h1>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>用户名</th>
<th>真实姓名</th>
<th>加入时间</th>
<th></th>
</tr>
<tr ms-repeat="member_list">
<td>{{ el.user.id }}</td>
<td>{{ el.user.username }}</td>
<td>{{ el.user.real_name }}</td>
<td>{{ el.join_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>
<button class="btn-sm btn-danger" ms-click="removeMember(el)">移除</button>
</td>
</tr>
</table>
<div class="text-right">
页数:{{ page }}/{{ page_count }}&nbsp;&nbsp;
<botton ms-attr-class="getBtnClass(0)" ms-click="getPrevious">上一页</botton>
<botton ms-attr-class="getBtnClass(1)" ms-click="getNext">下一页</botton>
</div>
<h1>修改小组信息</h1>
<div>
<div class="col-md-12">
<div class="form-group"><label>小组名</label>
<input type="text" name="name" class="form-control" ms-duplex="name">
</div>
</div>
<div class="col-md-12">
<div class="form-group"><label>描述</label>
<textarea rows="3" class="form-control" ms-duplex="description"></textarea>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>加入小组设置</label>
<input type="radio" name="join_group_setting" value="0" ms-duplex-checked="join_group_setting[0]">允许任何人加入
<input type="radio" name="join_group_setting" value="1" ms-duplex-checked="join_group_setting[1]">提交请求后管理员审核
<input type="radio" name="join_group_setting" value="2" ms-duplex-checked="join_group_setting[2]">不允许任何人加入
</div>
</div>
<div class="col-md-12">
<button class="btn btn-primary" ms-click="updateGroupInfo()">提交</button>
</div>
</div>
</div>
<script src="/static/js/app/admin/group/group_detail.js"></script>

View File

@ -0,0 +1,31 @@
<div ms-controller="request_list" class="col-md-9">
<h1>加入小组请求管理</h1>
<table class="table table-striped">
<tr>
<th>创建时间</th>
<th>用户</th>
<th>小组</th>
<th>附加消息</th>
<th></th>
</tr>
<tr ms-repeat="request_list">
<td>{{el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.group.name }}</td>
<td>{{ el.user.username }}</td>
<td>{{ el.message }}</td>
<td>
<button class="btn-sm btn-success" ms-click="processRequest(el.id, true)">同意</button>
<button class="btn-sm btn-danger" ms-click="processRequest(el.id, false)">拒绝</button>
</td>
</tr>
</table>
<div class="text-right">
页数:{{page}}/{{page_count}}&nbsp;&nbsp;
<botton ms-attr-class="getBtnClass(0)" ms-click="getPrevious">上一页</botton>
<botton ms-attr-class="getBtnClass(1)" ms-click="getNext">下一页</botton>
</div>
</div>
<script src="/static/js/app/admin/group/join_group_request_list.js"></script>

View File

@ -15,7 +15,6 @@
{% endifequal %}
</p>
<div>
<p>{{ announcement.content|safe }}</p>
</div>