add API support for user timezone and working hours
This commit is contained in:
parent
3d92e6967d
commit
3387c4be0f
4 changed files with 105 additions and 6 deletions
|
|
@ -1,3 +1,6 @@
|
|||
import time
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
|
||||
|
|
@ -9,6 +12,7 @@ from apps.base.utils import live_settings
|
|||
from apps.oss_installation.utils import cloud_user_identity_status
|
||||
from apps.twilioapp.utils import check_phone_number_is_valid
|
||||
from apps.user_management.models import User
|
||||
from apps.user_management.models.user import default_working_hours
|
||||
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
|
||||
from common.api_helpers.mixins import EagerLoadingMixin
|
||||
from common.constants.role import Role
|
||||
|
|
@ -29,6 +33,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
organization = FastOrganizationSerializer(read_only=True)
|
||||
current_team = TeamPrimaryKeyRelatedField(allow_null=True, required=False)
|
||||
|
||||
timezone = serializers.CharField(allow_null=True, required=False)
|
||||
avatar = serializers.URLField(source="avatar_url", read_only=True)
|
||||
|
||||
permissions = serializers.SerializerMethodField()
|
||||
|
|
@ -47,6 +52,8 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
"username",
|
||||
"role",
|
||||
"avatar",
|
||||
"timezone",
|
||||
"working_hours",
|
||||
"unverified_phone_number",
|
||||
"verified_phone_number",
|
||||
"slack_user_identity",
|
||||
|
|
@ -63,6 +70,49 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
|
|||
"verified_phone_number",
|
||||
]
|
||||
|
||||
def validate_timezone(self, tz):
|
||||
if tz is None:
|
||||
return tz
|
||||
|
||||
try:
|
||||
pytz.timezone(tz)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
raise serializers.ValidationError("not a valid timezone")
|
||||
|
||||
return tz
|
||||
|
||||
def validate_working_hours(self, working_hours):
|
||||
if not isinstance(working_hours, dict):
|
||||
raise serializers.ValidationError("must be dict")
|
||||
|
||||
# check that all days are present
|
||||
if sorted(working_hours.keys()) != sorted(default_working_hours().keys()):
|
||||
raise serializers.ValidationError("missing some days")
|
||||
|
||||
for day in working_hours:
|
||||
periods = working_hours[day]
|
||||
|
||||
for period in periods:
|
||||
if not isinstance(period, dict):
|
||||
raise serializers.ValidationError("period must be dict")
|
||||
|
||||
if sorted(period.keys()) != sorted(["start", "end"]):
|
||||
raise serializers.ValidationError("'start' and 'end' fields must be present")
|
||||
|
||||
if not isinstance(period["start"], str) or not isinstance(period["end"], str):
|
||||
raise serializers.ValidationError("'start' and 'end' fields must be str")
|
||||
|
||||
try:
|
||||
start = time.strptime(period["start"], "%H:%M:%S")
|
||||
end = time.strptime(period["end"], "%H:%M:%S")
|
||||
except ValueError:
|
||||
raise serializers.ValidationError("'start' and 'end' fields must be in '%H:%M:%S' format")
|
||||
|
||||
if start >= end:
|
||||
raise serializers.ValidationError("'start' must be less than 'end'")
|
||||
|
||||
return working_hours
|
||||
|
||||
def validate_unverified_phone_number(self, value):
|
||||
if value:
|
||||
if check_phone_number_is_valid(value):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytz
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
|
@ -123,7 +124,7 @@ class UserView(
|
|||
"mobile_app_verification_token",
|
||||
"mobile_app_auth_token",
|
||||
),
|
||||
AnyRole: ("retrieve",),
|
||||
AnyRole: ("retrieve", "timezone_options"),
|
||||
}
|
||||
|
||||
action_object_permissions = {
|
||||
|
|
@ -236,6 +237,10 @@ class UserView(
|
|||
serializer = UserSerializer(self.get_queryset().get(pk=self.request.user.pk))
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=["get"])
|
||||
def timezone_options(self, request):
|
||||
return Response(pytz.common_timezones)
|
||||
|
||||
@action(detail=True, methods=["get"])
|
||||
def get_verification_code(self, request, pk):
|
||||
user = self.get_object()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.2.13 on 2022-07-05 12:14
|
||||
|
||||
import apps.user_management.models.user
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0001_squashed_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='_timezone',
|
||||
field=models.CharField(default=None, max_length=50, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='working_hours',
|
||||
field=models.JSONField(default=apps.user_management.models.user.default_working_hours, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -30,6 +30,16 @@ def generate_public_primary_key_for_user():
|
|||
return new_public_primary_key
|
||||
|
||||
|
||||
def default_working_hours():
|
||||
weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday"]
|
||||
weekends = ["saturday", "sunday"]
|
||||
|
||||
working_hours = {day: [{"start": "09:00:00", "end": "17:00:00"}] for day in weekdays}
|
||||
working_hours |= {day: [] for day in weekends}
|
||||
|
||||
return working_hours
|
||||
|
||||
|
||||
class UserManager(models.Manager):
|
||||
@staticmethod
|
||||
def sync_for_team(team, api_members: list[dict]):
|
||||
|
|
@ -128,6 +138,10 @@ class User(models.Model):
|
|||
role = models.PositiveSmallIntegerField(choices=Role.choices())
|
||||
avatar_url = models.URLField()
|
||||
|
||||
# don't use "_timezone" directly, use the "timezone" property since it can be populated via slack user identity
|
||||
_timezone = models.CharField(max_length=50, null=True, default=None)
|
||||
working_hours = models.JSONField(null=True, default=default_working_hours)
|
||||
|
||||
notification = models.ManyToManyField("alerts.AlertGroup", through="alerts.UserHasNotification")
|
||||
|
||||
unverified_phone_number = models.CharField(max_length=20, null=True, default=None)
|
||||
|
|
@ -222,11 +236,17 @@ class User(models.Model):
|
|||
|
||||
@property
|
||||
def timezone(self):
|
||||
slack_user_identity = self.slack_user_identity
|
||||
if slack_user_identity:
|
||||
return slack_user_identity.timezone
|
||||
else:
|
||||
return None
|
||||
if self._timezone:
|
||||
return self._timezone
|
||||
|
||||
if self.slack_user_identity:
|
||||
return self.slack_user_identity.timezone
|
||||
|
||||
return None
|
||||
|
||||
@timezone.setter
|
||||
def timezone(self, value):
|
||||
self._timezone = value
|
||||
|
||||
def short(self):
|
||||
return {"username": self.username, "pk": self.public_primary_key, "avatar": self.avatar_url}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue