Merge branch 'zemal_dev'

This commit is contained in:
zema1 2017-12-01 20:15:57 +08:00
commit df0b0c1108
20 changed files with 144 additions and 85 deletions

2
.gitignore vendored
View File

@ -53,6 +53,7 @@ db.db
*.rdb *.rdb
#*.out #*.out
*.sqlite3 *.sqlite3
.python-version
.DS_Store .DS_Store
build.txt build.txt
tmp/ tmp/
@ -69,3 +70,4 @@ data/public/upload/*
!data/public/upload/.gitkeep !data/public/upload/.gitkeep
data/public/avatar/* data/public/avatar/*
!data/public/avatar/default.png !data/public/avatar/default.png

View File

@ -1 +0,0 @@
3.6.2

View File

@ -5,6 +5,8 @@ ENV OJ_ENV production
ADD . /app ADD . /app
WORKDIR /app WORKDIR /app
HEALTHCHECK --interval=5s --retries=3 CMD python2 /app/deploy/health_check.py
RUN printf "https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/community/\nhttps://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/main/" > /etc/apk/repositories && \ RUN printf "https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/community/\nhttps://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/main/" > /etc/apk/repositories && \
apk add --update --no-cache build-base nginx openssl curl unzip supervisor jpeg-dev zlib-dev postgresql-dev freetype-dev && \ apk add --update --no-cache build-base nginx openssl curl unzip supervisor jpeg-dev zlib-dev postgresql-dev freetype-dev && \
pip install --no-cache-dir -r /app/deploy/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \ pip install --no-cache-dir -r /app/deploy/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \
@ -12,4 +14,5 @@ RUN printf "https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/community/\nhttps:/
RUN curl -L $(curl -s https://api.github.com/repos/QingdaoU/OnlineJudgeFE/releases/latest | grep /dist.zip | cut -d '"' -f 4) -o dist.zip && \ RUN curl -L $(curl -s https://api.github.com/repos/QingdaoU/OnlineJudgeFE/releases/latest | grep /dist.zip | cut -d '"' -f 4) -o dist.zip && \
unzip dist.zip && \ unzip dist.zip && \
rm dist.zip rm dist.zip
CMD sh /app/deploy/run.sh CMD sh /app/deploy/run.sh

View File

@ -30,7 +30,11 @@
## 安装 ## 安装
文档: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0) 请根据此进行安装: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0)
## 文档
[http://docs.onlinejudge.me/](http://docs.onlinejudge.me/)
## 截图 ## 截图
@ -64,6 +68,11 @@ Rankings 中可以控制图表和菜单的显隐。
![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png) ![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png)
## 浏览器支持
Modern browsers(chrome, firefox) 和 Internet Explorer 10+.
## 特别感谢 ## 特别感谢
+ 所有为本项目做出贡献的人 + 所有为本项目做出贡献的人

View File

@ -32,6 +32,10 @@ The main modules are open source:
Follow me: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0) Follow me: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0)
## Documents
[http://docs.onlinejudge.me/](http://docs.onlinejudge.me/)
## Screenshots ## Screenshots
### Frontend: ### Frontend:
@ -64,6 +68,10 @@ You can control the menu and chart status in rankings.
![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png) ![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png)
## Browser Support
Modern browsers(chrome, firefox) 和 Internet Explorer 10+.
## Special Thanks ## Special Thanks
+ I'd appreciate a github star if you find this helpful + I'd appreciate a github star if you find this helpful

View File

@ -1,6 +1,6 @@
from django import forms from django import forms
from utils.api import DateTimeTZField, serializers, UsernameSerializer from utils.api import serializers, UsernameSerializer
from .models import AdminType, ProblemPermission, User, UserProfile from .models import AdminType, ProblemPermission, User, UserProfile
@ -50,8 +50,6 @@ class ImportUserSeralizer(serializers.Serializer):
class UserAdminSerializer(serializers.ModelSerializer): class UserAdminSerializer(serializers.ModelSerializer):
real_name = serializers.SerializerMethodField() real_name = serializers.SerializerMethodField()
create_time = DateTimeTZField()
last_login = DateTimeTZField()
class Meta: class Meta:
model = User model = User
@ -63,9 +61,6 @@ class UserAdminSerializer(serializers.ModelSerializer):
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
create_time = DateTimeTZField()
last_login = DateTimeTZField()
class Meta: class Meta:
model = User model = User
fields = ["id", "username", "email", "admin_type", "problem_permission", fields = ["id", "username", "email", "admin_type", "problem_permission",
@ -74,22 +69,12 @@ class UserSerializer(serializers.ModelSerializer):
class UserProfileSerializer(serializers.ModelSerializer): class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer() user = UserSerializer()
acm_problems_status = serializers.JSONField()
oi_problems_status = serializers.JSONField()
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = "__all__" fields = "__all__"
class UserInfoSerializer(serializers.ModelSerializer):
acm_problems_status = serializers.JSONField()
oi_problems_status = serializers.JSONField()
class Meta:
model = UserProfile
class EditUserSerializer(serializers.Serializer): class EditUserSerializer(serializers.Serializer):
id = serializers.IntegerField() id = serializers.IntegerField()
username = serializers.CharField(max_length=32) username = serializers.CharField(max_length=32)
@ -105,12 +90,12 @@ class EditUserSerializer(serializers.Serializer):
class EditUserProfileSerializer(serializers.Serializer): class EditUserProfileSerializer(serializers.Serializer):
real_name = serializers.CharField(max_length=32, allow_null=True, required=False) real_name = serializers.CharField(max_length=32, allow_null=True, required=False)
avatar = serializers.CharField(max_length=256, allow_null=True, allow_blank=True, required=False) avatar = serializers.CharField(max_length=256, allow_blank=True, required=False)
blog = serializers.URLField(max_length=256, allow_null=True, allow_blank=True, required=False) blog = serializers.URLField(max_length=256, allow_blank=True, required=False)
mood = serializers.CharField(max_length=256, allow_null=True, allow_blank=True, required=False) mood = serializers.CharField(max_length=256, allow_blank=True, required=False)
github = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) github = serializers.CharField(max_length=64, allow_blank=True, required=False)
school = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) school = serializers.CharField(max_length=64, allow_blank=True, required=False)
major = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) major = serializers.CharField(max_length=64, allow_blank=True, required=False)
class ApplyResetPasswordSerializer(serializers.Serializer): class ApplyResetPasswordSerializer(serializers.Serializer):
@ -142,3 +127,4 @@ class RankInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = "__all__"

View File

@ -1,5 +1,5 @@
from utils.api import serializers from utils.api import serializers
from utils.api._serializers import DateTimeTZField, UsernameSerializer from utils.api._serializers import UsernameSerializer
from .models import Announcement from .models import Announcement
@ -11,12 +11,11 @@ class CreateAnnouncementSerializer(serializers.Serializer):
class AnnouncementSerializer(serializers.ModelSerializer): class AnnouncementSerializer(serializers.ModelSerializer):
create_time = DateTimeTZField()
last_update_time = DateTimeTZField()
created_by = UsernameSerializer() created_by = UsernameSerializer()
class Meta: class Meta:
model = Announcement model = Announcement
fields = "__all__"
class EditAnnouncementSerializer(serializers.Serializer): class EditAnnouncementSerializer(serializers.Serializer):

View File

@ -1,4 +1,4 @@
from utils.api import DateTimeTZField, serializers from utils.api import serializers
from .models import JudgeServer from .models import JudgeServer
@ -29,8 +29,6 @@ class CreateEditWebsiteConfigSerializer(serializers.Serializer):
class JudgeServerSerializer(serializers.ModelSerializer): class JudgeServerSerializer(serializers.ModelSerializer):
create_time = DateTimeTZField()
last_heartbeat = DateTimeTZField()
status = serializers.CharField() status = serializers.CharField()
class Meta: class Meta:

View File

@ -1,4 +1,4 @@
from utils.api import DateTimeTZField, UsernameSerializer, serializers from utils.api import UsernameSerializer, serializers
from .models import Contest, ContestAnnouncement, ContestRuleType from .models import Contest, ContestAnnouncement, ContestRuleType
from .models import ACMContestRank, OIContestRank from .models import ACMContestRank, OIContestRank
@ -29,16 +29,13 @@ class EditConetestSeriaizer(serializers.Serializer):
class ContestAdminSerializer(serializers.ModelSerializer): class ContestAdminSerializer(serializers.ModelSerializer):
start_time = DateTimeTZField()
end_time = DateTimeTZField()
create_time = DateTimeTZField()
last_update_time = DateTimeTZField()
created_by = UsernameSerializer() created_by = UsernameSerializer()
status = serializers.CharField() status = serializers.CharField()
contest_type = serializers.CharField() contest_type = serializers.CharField()
class Meta: class Meta:
model = Contest model = Contest
fields = "__all__"
class ContestSerializer(ContestAdminSerializer): class ContestSerializer(ContestAdminSerializer):
@ -49,10 +46,10 @@ class ContestSerializer(ContestAdminSerializer):
class ContestAnnouncementSerializer(serializers.ModelSerializer): class ContestAnnouncementSerializer(serializers.ModelSerializer):
created_by = UsernameSerializer() created_by = UsernameSerializer()
create_time = DateTimeTZField()
class Meta: class Meta:
model = ContestAnnouncement model = ContestAnnouncement
fields = "__all__"
class CreateContestAnnouncementSerializer(serializers.Serializer): class CreateContestAnnouncementSerializer(serializers.Serializer):
@ -76,10 +73,10 @@ class ContestPasswordVerifySerializer(serializers.Serializer):
class ACMContestRankSerializer(serializers.ModelSerializer): class ACMContestRankSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField() user = serializers.SerializerMethodField()
submission_info = serializers.JSONField()
class Meta: class Meta:
model = ACMContestRank model = ACMContestRank
fields = "__all__"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.is_admin_role = kwargs.pop("is_admin_role", False) self.is_admin_role = kwargs.pop("is_admin_role", False)
@ -91,10 +88,10 @@ class ACMContestRankSerializer(serializers.ModelSerializer):
class OIContestRankSerializer(serializers.ModelSerializer): class OIContestRankSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField() user = serializers.SerializerMethodField()
submission_info = serializers.JSONField()
class Meta: class Meta:
model = OIContestRank model = OIContestRank
fields = "__all__"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.is_admin_role = kwargs.pop("is_admin_role", False) self.is_admin_role = kwargs.pop("is_admin_role", False)

View File

@ -3,7 +3,6 @@ from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
from utils.api._serializers import DateTimeTZField
from utils.api.tests import APITestCase from utils.api.tests import APITestCase
from .models import ContestAnnouncement, ContestRuleType, Contest from .models import ContestAnnouncement, ContestRuleType, Contest
@ -44,10 +43,9 @@ class ContestAdminAPITest(APITestCase):
response = self.client.put(self.url, data=data) response = self.client.put(self.url, data=data)
self.assertSuccess(response) self.assertSuccess(response)
response_data = response.data["data"] response_data = response.data["data"]
datetime_tz_field = DateTimeTZField()
for k in data.keys(): for k in data.keys():
if isinstance(data[k], datetime): if isinstance(data[k], datetime):
data[k] = datetime_tz_field.to_representation(data[k]) continue
self.assertEqual(response_data[k], data[k]) self.assertEqual(response_data[k], data[k])
def test_get_contests(self): def test_get_contests(self):

11
deploy/health_check.py Normal file
View File

@ -0,0 +1,11 @@
import xmlrpclib
if __name__ == "__main__":
try:
server = xmlrpclib.Server('http://localhost:9005/RPC2')
info = server.supervisor.getAllProcessInfo()
error_states = list(filter(lambda x: x["state"] != 20, info))
exit(len(error_states))
except Exception as e:
print(e.with_traceback())
exit(1)

View File

@ -0,0 +1,8 @@
FROM alpine:3.6
RUN apk add --update --no-cache rsync
ADD ./run.sh /tmp/run.sh
ADD ./rsyncd.conf /etc/rsyncd.conf
CMD /bin/sh /tmp/run.sh

View File

@ -0,0 +1,24 @@
version: "3"
services:
oj-rsync-master:
image: oj_rsync
container_name: oj-rsync
volumes:
- $PWD/data/backend/test_case:/test_case:ro
- $PWD/data/rsync_master:/log
environment:
- RSYNC_MODE=master
- RSYNC_USER=ojrsync
- RSYNC_PASSWORD=CHANGE_THIS_PASSWORD
ports:
- "0.0.0.0:873:873"
oj-rsync-slave:
image: oj-rsync
volumes:
- $PWD/test_case:/test_case
- $PWD/rsync_slave:/log
environment:
- RSYNC_MODE=slave
- RSYNC_USER=ojrsync
- RSYNC_PASSWORD=CHANGE_THIS_PASSWORD

View File

@ -0,0 +1,12 @@
port = 873
uid = root
gid = root
use chroot = yes
read only = yes
log file = /log/rsyncd.log
[testcase]
path = /test_case/
list = yes
auth users = ojrsync
secrets file = /etc/rsyncd.passwd

View File

@ -0,0 +1,33 @@
#!/usr/bin/env sh
slave_runner()
{
while true
do
rsync -avzP --delete --progress --password-file=/etc/rsync_slave.passwd $RSYNC_USER@$RSYNC_MASTER_ADDR::testcase /test_case >> /log/rsync_slave.log
sleep 5
done
}
master_runner()
{
rsync --daemon --config=/etc/rsyncd.conf
while true
do
sleep 60
done
}
if [ "$RSYNC_MODE" = "master" ]; then
if [ ! -f "/etc/rsyncd.passwd" ]; then
echo "$RSYNC_USER:$RSYNC_PASSWORD" > /etc/rsyncd.passwd
fi
chmod 600 /etc/rsyncd.passwd
master_runner
else
if [ ! -f "/etc/rsync_slave.passwd" ]; then
echo "$RSYNC_PASSWORD" > /etc/rsync_slave.passwd
fi
chmod 600 /etc/rsync_slave.passwd
slave_runner
fi

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from judge.languages import language_names, spj_language_names from judge.languages import language_names, spj_language_names
from utils.api import DateTimeTZField, UsernameSerializer, serializers from utils.api import UsernameSerializer, serializers
from .models import Problem, ProblemRuleType, ProblemTag from .models import Problem, ProblemRuleType, ProblemTag
from .utils import parse_problem_template from .utils import parse_problem_template
@ -87,15 +87,8 @@ class CompileSPJSerializer(serializers.Serializer):
class BaseProblemSerializer(serializers.ModelSerializer): class BaseProblemSerializer(serializers.ModelSerializer):
samples = serializers.JSONField()
test_case_score = serializers.JSONField()
languages = serializers.JSONField()
template = serializers.JSONField()
tags = serializers.SlugRelatedField(many=True, slug_field="name", read_only=True) tags = serializers.SlugRelatedField(many=True, slug_field="name", read_only=True)
create_time = DateTimeTZField()
last_update_time = DateTimeTZField()
created_by = UsernameSerializer() created_by = UsernameSerializer()
statistic_info = serializers.JSONField()
class ProblemAdminSerializer(BaseProblemSerializer): class ProblemAdminSerializer(BaseProblemSerializer):
@ -104,12 +97,6 @@ class ProblemAdminSerializer(BaseProblemSerializer):
fields = "__all__" fields = "__all__"
class ContestProblemAdminSerializer(BaseProblemSerializer):
class Meta:
model = Problem
fields = "__all__"
class ProblemSerializer(BaseProblemSerializer): class ProblemSerializer(BaseProblemSerializer):
template = serializers.SerializerMethodField() template = serializers.SerializerMethodField()
@ -121,21 +108,16 @@ class ProblemSerializer(BaseProblemSerializer):
class Meta: class Meta:
model = Problem model = Problem
exclude = ("contest", "test_case_score", "test_case_id", "visible", "is_public", exclude = ("test_case_score", "test_case_id", "visible", "is_public",
"template", "spj_code", "spj_version", "spj_compile_ok") "spj_code", "spj_version", "spj_compile_ok")
class ContestProblemSerializer(BaseProblemSerializer): class ProblemSafeSerializer(BaseProblemSerializer):
class Meta: class Meta:
model = Problem model = Problem
exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty") exclude = ("test_case_score", "test_case_id", "visible", "is_public",
"spj_code", "spj_version", "spj_compile_ok",
"difficulty", "submission_number", "accepted_number", "statistic_info")
class ContestProblemSafeSerializer(BaseProblemSerializer):
class Meta:
model = Problem
exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty",
"submission_number", "accepted_number", "statistic_info")
class ContestProblemMakePublicSerializer(serializers.Serializer): class ContestProblemMakePublicSerializer(serializers.Serializer):

View File

@ -16,7 +16,7 @@ from utils.api import APIView, CSRFExemptAPIView, validate_serializer
from utils.shortcuts import rand_str, natural_sort_key from utils.shortcuts import rand_str, natural_sort_key
from ..models import Problem, ProblemRuleType, ProblemTag from ..models import Problem, ProblemRuleType, ProblemTag
from ..serializers import (CreateContestProblemSerializer, ContestProblemAdminSerializer, CompileSPJSerializer, from ..serializers import (CreateContestProblemSerializer, CompileSPJSerializer,
CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer, CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer,
ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer) ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer)
@ -320,7 +320,7 @@ class ContestProblemAPI(ProblemBase):
except ProblemTag.DoesNotExist: except ProblemTag.DoesNotExist:
tag = ProblemTag.objects.create(name=item) tag = ProblemTag.objects.create(name=item)
problem.tags.add(tag) problem.tags.add(tag)
return self.success(ContestProblemAdminSerializer(problem).data) return self.success(ProblemAdminSerializer(problem).data)
@problem_permission_required @problem_permission_required
def get(self, request): def get(self, request):
@ -345,7 +345,7 @@ class ContestProblemAPI(ProblemBase):
keyword = request.GET.get("keyword") keyword = request.GET.get("keyword")
if keyword: if keyword:
problems = problems.filter(title__contains=keyword) problems = problems.filter(title__contains=keyword)
return self.success(self.paginate_data(request, problems, ContestProblemAdminSerializer)) return self.success(self.paginate_data(request, problems, ProblemAdminSerializer))
@validate_serializer(EditContestProblemSerializer) @validate_serializer(EditContestProblemSerializer)
@problem_permission_required @problem_permission_required

View File

@ -3,8 +3,7 @@ from django.db.models import Q, Count
from utils.api import APIView from utils.api import APIView
from account.decorators import check_contest_permission from account.decorators import check_contest_permission
from ..models import ProblemTag, Problem, ProblemRuleType from ..models import ProblemTag, Problem, ProblemRuleType
from ..serializers import ProblemSerializer, TagSerializer from ..serializers import ProblemSerializer, TagSerializer, ProblemSafeSerializer
from ..serializers import ContestProblemSerializer, ContestProblemSafeSerializer
from contest.models import ContestRuleType from contest.models import ContestRuleType
@ -102,16 +101,16 @@ class ContestProblemAPI(APIView):
except Problem.DoesNotExist: except Problem.DoesNotExist:
return self.error("Problem does not exist.") return self.error("Problem does not exist.")
if self.contest.problem_details_permission(request.user): if self.contest.problem_details_permission(request.user):
problem_data = ContestProblemSerializer(problem).data problem_data = ProblemSerializer(problem).data
self._add_problem_status(request, [problem_data, ]) self._add_problem_status(request, [problem_data, ])
else: else:
problem_data = ContestProblemSafeSerializer(problem).data problem_data = ProblemSafeSerializer(problem).data
return self.success(problem_data) return self.success(problem_data)
contest_problems = Problem.objects.select_related("created_by").filter(contest=self.contest, visible=True) contest_problems = Problem.objects.select_related("created_by").filter(contest=self.contest, visible=True)
if self.contest.problem_details_permission(request.user): if self.contest.problem_details_permission(request.user):
data = ContestProblemSerializer(contest_problems, many=True).data data = ProblemSerializer(contest_problems, many=True).data
self._add_problem_status(request, data) self._add_problem_status(request, data)
else: else:
data = ContestProblemSafeSerializer(contest_problems, many=True).data data = ProblemSafeSerializer(contest_problems, many=True).data
return self.success(data) return self.success(data)

View File

@ -17,17 +17,15 @@ class ShareSubmissionSerializer(serializers.Serializer):
class SubmissionModelSerializer(serializers.ModelSerializer): class SubmissionModelSerializer(serializers.ModelSerializer):
info = serializers.JSONField()
statistic_info = serializers.JSONField()
class Meta: class Meta:
model = Submission model = Submission
fields = "__all__"
# 不显示submission info的serializer, 用于ACM rule_type # 不显示submission info的serializer, 用于ACM rule_type
class SubmissionSafeModelSerializer(serializers.ModelSerializer): class SubmissionSafeModelSerializer(serializers.ModelSerializer):
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")
statistic_info = serializers.JSONField()
class Meta: class Meta:
model = Submission model = Submission
@ -36,7 +34,6 @@ class SubmissionSafeModelSerializer(serializers.ModelSerializer):
class SubmissionListSerializer(serializers.ModelSerializer): class SubmissionListSerializer(serializers.ModelSerializer):
problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") problem = serializers.SlugRelatedField(read_only=True, slug_field="_id")
statistic_info = serializers.JSONField()
show_link = serializers.SerializerMethodField() show_link = serializers.SerializerMethodField()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -1,12 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
class DateTimeTZField(serializers.DateTimeField):
def to_representation(self, value):
# value = timezone.localtime(value)
return super(DateTimeTZField, self).to_representation(value)
class UsernameSerializer(serializers.Serializer): class UsernameSerializer(serializers.Serializer):
id = serializers.IntegerField() id = serializers.IntegerField()
username = serializers.CharField() username = serializers.CharField()