Skip to content

Commit

Permalink
Merge pull request #6 from Team-MailedIt/develop
Browse files Browse the repository at this point in the history
1차 최종본
  • Loading branch information
kynzun authored Dec 5, 2021
2 parents 377ad2e + 6673b94 commit 4902fef
Show file tree
Hide file tree
Showing 19 changed files with 424 additions and 57 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# django-vote-14th
프론트엔도와 백엔드가 함께 투표 어플리케이션을 만들어봅시다!

## 마지막 과제(최초기한: 12/5 일요일까지)
#### 참고노션 : https://ceos-14th-backend-study.notion.site/1901c0c01bc843b98de7f5a0c9d811fb
# MailedIt 15기 파트장 투표 서비스
- [투표하러 가기](https://vote-mailedit.vercel.app/)
- [API 문서](https://documenter.getpostman.com/view/17298535/UVJbHd2g)
- ERD\
![](src/django-vote-erd.png)
Empty file added account/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions account/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin
from .models import User, Candidate, Vote

# Register your models here.
admin.site.register(User)
admin.site.register(Candidate)
admin.site.register(Vote)
5 changes: 5 additions & 0 deletions account/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AccountConfig(AppConfig):
name = 'account'
45 changes: 45 additions & 0 deletions account/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 3.0.8 on 2021-11-25 14:25

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0011_update_proxy_permissions'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('part', models.CharField(choices=[('FE', '프론트엔드'), ('BE', '백엔드')], max_length=2)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
31 changes: 31 additions & 0 deletions account/migrations/0002_candidate_vote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 3.0.8 on 2021-12-02 17:01

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('account', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Candidate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=30)),
('part', models.CharField(choices=[('FE', '프론트엔드'), ('BE', '백엔드')], max_length=2)),
],
),
migrations.CreateModel(
name='Vote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vote_candidate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Candidate')),
('vote_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file added account/migrations/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions account/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager

PART_CHOICES = [
("FE", "프론트엔드"),
("BE", "백엔드"),
]


class User(AbstractUser):
part = models.CharField(max_length=2, choices=PART_CHOICES)


class Candidate(models.Model):
name = models.CharField(max_length=30)
part = models.CharField(max_length=2, choices=PART_CHOICES)

def __str__(self):
return self.name


class Vote(models.Model):
vote_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="user_votes"
)
vote_candidate = models.ForeignKey(
Candidate, on_delete=models.CASCADE, related_name="cand_votes"
)

def __str__(self):
return f"{self.vote_user.username}'s vote on {self.vote_candidate.name}"
48 changes: 48 additions & 0 deletions account/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.db.models.base import Model
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import Candidate, Vote

User = get_user_model()


class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "password", "part")

def create(self, validated_data):
user = User(
username=validated_data.get("username"),
part=validated_data.get("part"),
)
user.set_password(validated_data.get("password"))
user.save()
return user


class VoteSerializer(serializers.ModelSerializer):
vote_user = serializers.SerializerMethodField()
vote_candidate = serializers.SerializerMethodField()

def get_vote_user(self, obj):
return obj.vote_user.username

def get_vote_candidate(self, obj):
return obj.vote_candidate.name

class Meta:
model = Vote
fields = ["id", "vote_user", "vote_candidate"]


class CandidateSerializer(serializers.ModelSerializer):
cand_votes = VoteSerializer(many=True)
vote_count = serializers.SerializerMethodField()

class Meta:
model = Candidate
fields = ["id", "name", "part", "vote_count", "cand_votes"]

def get_vote_count(self, obj):
return obj.cand_votes.count()
3 changes: 3 additions & 0 deletions account/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
16 changes: 16 additions & 0 deletions account/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from .views import *

urlpatterns = [
# path("signin", TokenObtainPairView.as_view(), name="token_obtain_pair"),
# 세션 연장하고 싶을 때 refresh token 사용
# path("token/refresh", TokenRefreshView.as_view(), name="token_refresh"),
path("signin", AuthView.as_view()),
path("signup", RegisterAPIView.as_view()),
path("candidate", CandidateListAPIView.as_view()),
path("candidate/result", CandidateResultAPIView.as_view()),
path("candidate/<int:pk>", CandidateDetailAPIView.as_view()),
path("test", TestAPIView.as_view()),
path("testauth", TestAuthAPIView.as_view()),
]
155 changes: 155 additions & 0 deletions account/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from django.http.response import Http404
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status, permissions
from django.contrib.auth import get_user_model, authenticate
from .models import Candidate, Vote
from .serializers import RegisterSerializer, CandidateSerializer, VoteSerializer
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from django.db.models import Count

User = get_user_model()

# 회원가입
class RegisterAPIView(APIView):
def post(self, request):
user_serializer = RegisterSerializer(data=request.data)
if user_serializer.is_valid():
user = user_serializer.save()
# access jwt token
token = TokenObtainPairSerializer.get_token(user)
refresh_token = str(token)
access_token = str(token.access_token)
res = Response(
{
"user": user_serializer.data,
"message": "Successfully registered user",
"token": {
"refresh": refresh_token,
"access": access_token,
},
},
status=status.HTTP_200_OK,
)
res.set_cookie("access", access_token, httponly=True)
res.set_cookie("refresh", refresh_token, httponly=True)
return res
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)


# 로그인
class AuthView(APIView):
# 유저정보 확인
def get(self, request):
pass

# 로그인
def post(self, request):
user = authenticate(
username=request.data.get("username"), password=request.data.get("password")
)
if user is not None:
token = TokenObtainPairSerializer.get_token(user)
refresh_token = str(token)
access_token = str(token.access_token)
res = Response(
{
"user": {
"username": user.username,
"password": user.password,
"part": user.part,
},
"message": "Successfully logged in",
"token": {
"refresh": refresh_token,
"access": access_token,
},
},
status=status.HTTP_200_OK,
)
res.set_cookie("access", access_token, httponly=True)
res.set_cookie("refresh", refresh_token, httponly=True)
return res
else:
return Response(status=status.HTTP_400_BAD_REQUEST)


class CandidateListAPIView(APIView):
def get(self, request, format=None):
candidates = Candidate.objects.all()
part = request.query_params.get("part", None)
if part is not None:
candidates = candidates.filter(part=part)

# 이름순 정렬
candidates = candidates.order_by("name")
serializer = CandidateSerializer(candidates, many=True)
return Response(serializer.data, status.HTTP_200_OK)


class CandidateResultAPIView(APIView):
def get(self, request, format=None):
candidates = Candidate.objects.all()
part = request.GET.get("part", None)
if part is not None:
candidates = candidates.filter(part=part)

# 득표순, 이름순으로 정렬
candidates = candidates.annotate(vote_count=Count("cand_votes")).order_by(
"-vote_count", "name"
)
serializer = CandidateSerializer(candidates, many=True)
return Response(serializer.data, status.HTTP_200_OK)


class CandidateDetailAPIView(APIView):
permission_classes = (IsAuthenticatedOrReadOnly,)

def get_object(self, pk):
try:
return Candidate.objects.get(pk=pk)
except Candidate.DoesNotExist:
raise Http404

def get(self, request, pk, format=None):
candidate = self.get_object(pk)
serializer = CandidateSerializer(candidate)
return Response(serializer.data, status.HTTP_200_OK)

def post(self, request, pk, format=None):
try:
user = request.user
candidate = self.get_object(pk)
vote = Vote(vote_user=user, vote_candidate=candidate)
vote.save()
serializer = VoteSerializer(vote)
return Response(
{
"vote": serializer.data,
"message": "Successfully voted",
},
status=status.HTTP_200_OK,
)
except:
return Response(status.HTTP_500_INTERNAL_SERVER_ERROR)


class TestAPIView(APIView):
def get(self, request):
return Response(
{"message": "test API successfully responsed"}, status=status.HTTP_200_OK
)


# 로그인했을 때만 가능한 요청
class TestAuthAPIView(APIView):
permission_classes = [
permissions.IsAuthenticated,
]

def get(self, request):
return Response(
{"message": "test Auth API successfully responsed"},
status=status.HTTP_200_OK,
)
4 changes: 4 additions & 0 deletions config/docker/entrypoint.prod.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#!/bin/sh

echo "Collect static files"
python manage.py collectstatic --no-input

echo "Apply database migrations"
python manage.py migrate

exec "$@"
3 changes: 1 addition & 2 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
version: '3.8'
version: "3.8"
services:

web:
container_name: web
build:
Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ asgiref==3.4.1
cffi==1.15.0
cryptography==35.0.0
Django==3.0.8
django-cors-headers==3.10.0
django-environ==0.4.5
djangorestframework==3.12.4
djangorestframework-simplejwt==5.0.0
gunicorn==20.1.0
pycparser==2.21
PyJWT==2.3.0
PyMySQL==1.0.2
pytz==2021.3
sqlparse==0.4.2
Binary file added src/django-vote-erd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4902fef

Please sign in to comment.