From 106699d5ea95beca71cc4e52d5220ec3f0a3167e Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 00:06:51 +0900 Subject: [PATCH 1/6] Add affiliated company to user group --- ..._add_affiliated_company_to_user_profile.py | 49 ++++++++++ apps/user/models/user_profile.py | 91 ++++++++++--------- 2 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 apps/user/migrations/0018_add_affiliated_company_to_user_profile.py diff --git a/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py b/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py new file mode 100644 index 00000000..093f05eb --- /dev/null +++ b/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.13 on 2022-10-12 11:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("user", "0017_add_user_group_options"), + ] + + operations = [ + migrations.AlterField( + model_name="manualuser", + name="org_type", + field=models.IntegerField( + choices=[ + (0, "Unauthorized user"), + (1, "KAIST member"), + (2, "Store employee"), + (3, "Other member"), + (4, "KAIST organization"), + (5, "External organization"), + (6, "Communication board admin"), + (7, "News board admin"), + (8, "Affiliated company"), + ], + default=0, + ), + ), + migrations.AlterField( + model_name="userprofile", + name="group", + field=models.IntegerField( + choices=[ + (0, "Unauthorized user"), + (1, "KAIST member"), + (2, "Store employee"), + (3, "Other member"), + (4, "KAIST organization"), + (5, "External organization"), + (6, "Communication board admin"), + (7, "News board admin"), + (8, "Affiliated company"), + ], + default=0, + ), + ), + ] diff --git a/apps/user/models/user_profile.py b/apps/user/models/user_profile.py index 8c8409c2..43c41343 100644 --- a/apps/user/models/user_profile.py +++ b/apps/user/models/user_profile.py @@ -2,8 +2,8 @@ from cached_property import cached_property from dateutil.relativedelta import relativedelta -from django.db import models from django.conf import settings +from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy from django_mysql.models import JSONField @@ -14,23 +14,33 @@ class UserProfile(MetaDataModel): class Meta(MetaDataModel.Meta): - verbose_name = '유저 프로필' - verbose_name_plural = '유저 프로필 목록' + verbose_name = "유저 프로필" + verbose_name_plural = "유저 프로필 목록" unique_together = ( - ('uid', 'deleted_at'), - ('sid', 'deleted_at'), - ('nickname', 'is_newara', 'deleted_at'), + ("uid", "deleted_at"), + ("sid", "deleted_at"), + ("nickname", "is_newara", "deleted_at"), ) class UserGroup(models.IntegerChoices): - UNAUTHORIZED = 0, ugettext_lazy('Unauthorized user') # 뉴아라 계정을 만들지 않은 사람들 - KAIST_MEMBER = 1, ugettext_lazy('KAIST member') # 카이스트 메일을 가진 사람 (학생, 교직원) - STORE_EMPLOYEE = 2, ugettext_lazy('Store employee') # 교내 입주 업체 직원 - OTHER_MEMBER = 3, ugettext_lazy('Other member') # 카이스트 메일이 없는 개인 (특수한 관련자 등) - KAIST_ORG = 4, ugettext_lazy('KAIST organization') # 교내 학생 단체들 - EXTERNAL_ORG = 5, ugettext_lazy('External organization') # 외부인 (홍보 계정 등) - COMMUNICATION_BOARD_ADMIN = 6, ugettext_lazy('Communication board admin') # 소통게시판 관리인 - NEWS_BOARD_ADMIN = 7, ugettext_lazy('News board admin') # 뉴스게시판 관리인 + # 뉴아라 계정을 만들지 않은 사람들 + UNAUTHORIZED = 0, ugettext_lazy("Unauthorized user") + # 카이스트 메일을 가진 사람 (학생, 교직원) + KAIST_MEMBER = 1, ugettext_lazy("KAIST member") + # 교내 입주 업체 직원 + STORE_EMPLOYEE = 2, ugettext_lazy("Store employee") + # 카이스트 메일이 없는 개인 (특수한 관련자 등) + OTHER_MEMBER = 3, ugettext_lazy("Other member") + # 교내 학생 단체들 + KAIST_ORG = 4, ugettext_lazy("KAIST organization") + # 외부인 (홍보 계정 등) + EXTERNAL_ORG = 5, ugettext_lazy("External organization") + # 소통게시판 관리인 + COMMUNICATION_BOARD_ADMIN = 6, ugettext_lazy("Communication board admin") + # 뉴스게시판 관리인 + NEWS_BOARD_ADMIN = 7, ugettext_lazy("News board admin") + # 제휴 업체 + AFFILIATED_COMPANY = 8, ugettext_lazy("Affiliated company") OFFICIAL_GROUPS = [UserGroup.STORE_EMPLOYEE, UserGroup.KAIST_ORG] @@ -39,86 +49,77 @@ class UserGroup(models.IntegerChoices): default=None, editable=False, max_length=30, - verbose_name='Sparcs SSO uid', + verbose_name="Sparcs SSO uid", ) sid = models.CharField( null=True, default=None, editable=False, max_length=30, - verbose_name='Sparcs SSO sid', + verbose_name="Sparcs SSO sid", ) sso_user_info = JSONField( editable=False, - verbose_name='Sparcs SSO 정보', + verbose_name="Sparcs SSO 정보", ) - picture = models.ImageField( null=True, blank=True, default=None, - upload_to='user_profiles/pictures', - verbose_name='프로필', + upload_to="user_profiles/pictures", + verbose_name="프로필", ) nickname = models.CharField( blank=True, - default='', + default="", max_length=128, - verbose_name='닉네임', + verbose_name="닉네임", ) nickname_updated_at = models.DateTimeField( - default=MIN_TIME, - verbose_name='최근 닉네임 변경일시' + default=MIN_TIME, verbose_name="최근 닉네임 변경일시" ) see_sexual = models.BooleanField( default=False, - verbose_name='성인/음란성 보기', + verbose_name="성인/음란성 보기", ) see_social = models.BooleanField( default=False, - verbose_name='정치/사회성 보기', + verbose_name="정치/사회성 보기", ) extra_preferences = JSONField( editable=False, - verbose_name='기타 설정', + verbose_name="기타 설정", ) - group = models.IntegerField( - choices=UserGroup.choices, - default=UserGroup.UNAUTHORIZED + choices=UserGroup.choices, default=UserGroup.UNAUTHORIZED ) - user = models.OneToOneField( on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL, - related_name='profile', - verbose_name='사용자', + related_name="profile", + verbose_name="사용자", primary_key=True, ) - # 포탈 공지에서 긁어온 작성자 or 이전한 아라 사용자는 is_newara=False is_newara = models.BooleanField( default=True, - verbose_name='뉴아라 사용자', + verbose_name="뉴아라 사용자", ) - ara_id = models.CharField( blank=True, - default='', + default="", max_length=128, - verbose_name='이전 아라 아이디', + verbose_name="이전 아라 아이디", ) - agree_terms_of_service_at = models.DateTimeField( null=True, default=None, - verbose_name='약관 동의 일시', + verbose_name="약관 동의 일시", ) - inactive_due_at = models.DateTimeField( null=True, default=None, - verbose_name='활동정지 마감 일시', + verbose_name="활동정지 마감 일시", ) def __str__(self) -> str: @@ -134,7 +135,11 @@ def email(self) -> str: @cached_property def realname(self) -> str: sso_info = self.sso_user_info - user_realname = json.loads(sso_info["kaist_info"])["ku_kname"] if sso_info["kaist_info"] else sso_info["last_name"] + sso_info["first_name"] + user_realname = ( + json.loads(sso_info["kaist_info"])["ku_kname"] + if sso_info["kaist_info"] + else sso_info["last_name"] + sso_info["first_name"] + ) return user_realname From ca6769f149506c3071f168f17f7ee88bfb7b30d7 Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 15:07:44 +0900 Subject: [PATCH 2/6] Use auto() with Enum --- apps/core/models/board.py | 100 +++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/apps/core/models/board.py b/apps/core/models/board.py index a589ad93..dc590dbd 100644 --- a/apps/core/models/board.py +++ b/apps/core/models/board.py @@ -1,51 +1,51 @@ +from enum import IntEnum, auto + from django.db import models -from enum import IntEnum from django_extensions.db.fields import AutoSlugField from ara.db.models import MetaDataModel class BoardNameType(IntEnum): - REGULAR = 0 - ANONYMOUS = 1 - REALNAME = 2 + REGULAR = auto() + ANONYMOUS = auto() + REALNAME = auto() class BoardAccessPermissionType(IntEnum): - READ = 0 - WRITE = 1 - COMMENT = 2 + READ = auto() + WRITE = auto() + COMMENT = auto() class Board(MetaDataModel): class Meta(MetaDataModel.Meta): - verbose_name = '게시판' - verbose_name_plural = '게시판 목록' + verbose_name = "게시판" + verbose_name_plural = "게시판 목록" unique_together = ( - ('ko_name', 'deleted_at'), - ('en_name', 'deleted_at'), + ("ko_name", "deleted_at"), + ("en_name", "deleted_at"), ) slug = AutoSlugField( populate_from=[ - 'en_name', + "en_name", ], ) ko_name = models.CharField( max_length=32, - verbose_name='게시판 국문 이름', + verbose_name="게시판 국문 이름", ) en_name = models.CharField( max_length=32, - verbose_name='게시판 영문 이름', + verbose_name="게시판 영문 이름", ) ko_description = models.TextField( - verbose_name='게시판 국문 소개', + verbose_name="게시판 국문 소개", ) en_description = models.TextField( - verbose_name='게시판 영문 소개', + verbose_name="게시판 영문 소개", ) - # 사용자 그룹에 대해 접근 권한을 제어하는 bit mask 입니다. # access_mask & (1 << user.group) > 0 일 때 접근이 가능합니다. # 사용자 그룹의 값들은 `UserGroup`을 참고하세요. @@ -53,86 +53,74 @@ class Meta(MetaDataModel.Meta): # UNAUTHORIZED, EXTERNAL_ORG 제외 모든 사용자 읽기 권한 부여 default=0b11011110, null=False, - verbose_name='읽기 권한' + verbose_name="읽기 권한", ) write_access_mask = models.SmallIntegerField( # UNAUTHORIZED, STORE_EMPLOYEE, EXTERNAL_ORG 제외 모든 사용자 쓰기 권한 부여 default=0b11011010, null=False, - verbose_name='쓰기 권한' + verbose_name="쓰기 권한", ) comment_access_mask = models.SmallIntegerField( # UNAUTHORIZED 제외 모든 사용자 댓글 권한 부여 - default=0b11111110, - null=False, - verbose_name='댓글 권한' + default=0b11111110, + null=False, + verbose_name="댓글 권한", ) - is_readonly = models.BooleanField( - verbose_name='읽기 전용 게시판', - help_text='활성화했을 때 관리자만 글을 쓸 수 있습니다. (ex. 포탈공지)', - default=False + verbose_name="읽기 전용 게시판", + help_text="활성화했을 때 관리자만 글을 쓸 수 있습니다. (ex. 포탈공지)", + default=False, ) is_hidden = models.BooleanField( - verbose_name='리스트 숨김 게시판', - help_text='활성화했을 때 메인페이지 상단바 리스트에 나타나지 않습니다. (ex. 뉴아라공지)', + verbose_name="리스트 숨김 게시판", + help_text="활성화했을 때 메인페이지 상단바 리스트에 나타나지 않습니다. (ex. 뉴아라공지)", default=False, db_index=True, ) - name_type = models.SmallIntegerField( - verbose_name='익명/실명 게시판', - help_text='게시판의 글과 댓글들이 익명 혹은 실명이도록 합니다.', + verbose_name="익명/실명 게시판", + help_text="게시판의 글과 댓글들이 익명 혹은 실명이도록 합니다.", default=BoardNameType.REGULAR, - db_index=True + db_index=True, ) - is_school_communication = models.BooleanField( - verbose_name='학교와의 소통 게시판', - help_text='학교 소통 게시판 글임을 표시', + verbose_name="학교와의 소통 게시판", + help_text="학교 소통 게시판 글임을 표시", default=False, - db_index=True - ) - - group_id = models.IntegerField( - verbose_name='그룹 ID', - default=1 + db_index=True, ) - + group_id = models.IntegerField(verbose_name="그룹 ID", default=1) banner_image = models.ImageField( - default='default_banner.png', - upload_to='board_banner_images', - verbose_name='게시판 배너 이미지', + default="default_banner.png", + upload_to="board_banner_images", + verbose_name="게시판 배너 이미지", ) - ko_banner_description = models.TextField( null=True, blank=True, default=None, - verbose_name='게시판 배너에 삽입되는 국문 소개', + verbose_name="게시판 배너에 삽입되는 국문 소개", ) - en_banner_description = models.TextField( null=True, blank=True, default=None, - verbose_name='게시판 배너에 삽입되는 영문 소개', + verbose_name="게시판 배너에 삽입되는 영문 소개", ) - banner_url = models.TextField( null=True, blank=True, default=None, - verbose_name='게시판 배너를 클릭 시에 이동하는 링크', + verbose_name="게시판 배너를 클릭 시에 이동하는 링크", ) def __str__(self) -> str: return self.ko_name - + def group_has_access_permission( - self, - access_type: BoardAccessPermissionType, - group: int) -> bool: + self, access_type: BoardAccessPermissionType, group: int + ) -> bool: mask = None if access_type == BoardAccessPermissionType.READ: mask = self.read_access_mask @@ -143,5 +131,5 @@ def group_has_access_permission( else: # TODO: Handle error return False - + return (mask & (1 << group)) > 0 From c8b5a46902fa036d2886dcb62ac72f7a59a4977b Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 15:15:24 +0900 Subject: [PATCH 3/6] Add zeros at the head of access masks --- apps/core/models/board.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/core/models/board.py b/apps/core/models/board.py index dc590dbd..a6c34ddd 100644 --- a/apps/core/models/board.py +++ b/apps/core/models/board.py @@ -51,19 +51,19 @@ class Meta(MetaDataModel.Meta): # 사용자 그룹의 값들은 `UserGroup`을 참고하세요. read_access_mask = models.SmallIntegerField( # UNAUTHORIZED, EXTERNAL_ORG 제외 모든 사용자 읽기 권한 부여 - default=0b11011110, + default=0b011011110, null=False, verbose_name="읽기 권한", ) write_access_mask = models.SmallIntegerField( # UNAUTHORIZED, STORE_EMPLOYEE, EXTERNAL_ORG 제외 모든 사용자 쓰기 권한 부여 - default=0b11011010, + default=0b011011010, null=False, verbose_name="쓰기 권한", ) comment_access_mask = models.SmallIntegerField( # UNAUTHORIZED 제외 모든 사용자 댓글 권한 부여 - default=0b11111110, + default=0b011111110, null=False, verbose_name="댓글 권한", ) From dfacea0a4a25f8a77b1a305ad659ac8b32b25698 Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 17:41:43 +0900 Subject: [PATCH 4/6] Remove affiliated company from user group --- ..._add_affiliated_company_to_user_profile.py | 49 ------------------- apps/user/models/user_profile.py | 2 - 2 files changed, 51 deletions(-) delete mode 100644 apps/user/migrations/0018_add_affiliated_company_to_user_profile.py diff --git a/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py b/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py deleted file mode 100644 index 093f05eb..00000000 --- a/apps/user/migrations/0018_add_affiliated_company_to_user_profile.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 3.2.13 on 2022-10-12 11:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("user", "0017_add_user_group_options"), - ] - - operations = [ - migrations.AlterField( - model_name="manualuser", - name="org_type", - field=models.IntegerField( - choices=[ - (0, "Unauthorized user"), - (1, "KAIST member"), - (2, "Store employee"), - (3, "Other member"), - (4, "KAIST organization"), - (5, "External organization"), - (6, "Communication board admin"), - (7, "News board admin"), - (8, "Affiliated company"), - ], - default=0, - ), - ), - migrations.AlterField( - model_name="userprofile", - name="group", - field=models.IntegerField( - choices=[ - (0, "Unauthorized user"), - (1, "KAIST member"), - (2, "Store employee"), - (3, "Other member"), - (4, "KAIST organization"), - (5, "External organization"), - (6, "Communication board admin"), - (7, "News board admin"), - (8, "Affiliated company"), - ], - default=0, - ), - ), - ] diff --git a/apps/user/models/user_profile.py b/apps/user/models/user_profile.py index 43c41343..9b8b7429 100644 --- a/apps/user/models/user_profile.py +++ b/apps/user/models/user_profile.py @@ -39,8 +39,6 @@ class UserGroup(models.IntegerChoices): COMMUNICATION_BOARD_ADMIN = 6, ugettext_lazy("Communication board admin") # 뉴스게시판 관리인 NEWS_BOARD_ADMIN = 7, ugettext_lazy("News board admin") - # 제휴 업체 - AFFILIATED_COMPANY = 8, ugettext_lazy("Affiliated company") OFFICIAL_GROUPS = [UserGroup.STORE_EMPLOYEE, UserGroup.KAIST_ORG] From b0823360591380bb39c3d3b6aecf425dd94a1efa Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 17:49:42 +0900 Subject: [PATCH 5/6] Remove board pagination --- .pre-commit-config.yaml | 1 + apps/core/views/viewsets/board.py | 19 +++++++------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32a82dba..2ce2d621 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,4 @@ repos: rev: 5.10.1 hooks: - id: isort + args: [--profile=black] diff --git a/apps/core/views/viewsets/board.py b/apps/core/views/viewsets/board.py index 9b491998..c59a8083 100644 --- a/apps/core/views/viewsets/board.py +++ b/apps/core/views/viewsets/board.py @@ -1,21 +1,16 @@ -from rest_framework import viewsets, permissions - -from ara.classes.viewset import ActionAPIViewSet +from rest_framework import permissions, viewsets from apps.core.models import Board -from apps.core.serializers.board import ( - BoardSerializer, - BoardDetailActionSerializer, -) +from apps.core.serializers.board import BoardDetailActionSerializer, BoardSerializer +from ara.classes.viewset import ActionAPIViewSet class BoardViewSet(viewsets.ReadOnlyModelViewSet, ActionAPIViewSet): + pagination_class = None queryset = Board.objects.all().reverse() - filterset_fields = ['is_readonly', 'is_hidden'] + filterset_fields = ["is_readonly", "is_hidden"] serializer_class = BoardSerializer - permission_classes = ( - permissions.IsAuthenticated, - ) + permission_classes = (permissions.IsAuthenticated,) action_serializer_class = { - 'list': BoardDetailActionSerializer, + "list": BoardDetailActionSerializer, } From 12ded256990c6157ee2b50c6aaecf8c6a0e003ae Mon Sep 17 00:00:00 2001 From: Injoon Hwang Date: Thu, 13 Oct 2022 21:45:27 +0900 Subject: [PATCH 6/6] Update board test --- tests/test_board.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/test_board.py b/tests/test_board.py index 1ac9908e..a7ba0831 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -1,18 +1,25 @@ import pytest +from rest_framework import status + from apps.core.models import Board from tests.conftest import RequestSetting, TestCase -@pytest.mark.usefixtures('set_user_client') +@pytest.mark.usefixtures("set_user_client") class TestBoard(TestCase, RequestSetting): - def test_list(self): - Board.objects.create(ko_name='자유 게시판', - en_name='Free Board', - ko_description='자유 게시판 입니다.', - en_description='This is a free board.') + Board.objects.create( + ko_name="자유 게시판", + en_name="Free Board", + ko_description="자유 게시판 입니다.", + en_description="This is a free board.", + ) + + boards = self.http_request(self.user, "get", "boards") + + assert boards.status_code == status.HTTP_200_OK + assert len(boards.data) == 1 - boards = self.http_request(self.user, 'get', 'boards') + (board,) = boards.data - assert boards.data.get('num_items') == 1 - assert boards.data.get('results')[0].get('en_name') == 'Free Board' + assert board.get("en_name") == "Free Board"