class NotificationViewSet( viewsets.GenericViewSet, viewsets.mixins.ListModelMixin, ): serializer_class = NotificationSerializer permission_classes = (IsAuthenticated,) filterset_fields = ('unread',) def get_queryset(self): return self.request.user.notifications.all() @action(methods=['GET'], detail=False, url_path='unread-count') @method_decorator(ratelimit(key='user', rate='3/s', method='GET', block=True)) def unread_count(self, request, *args, **kwargs): count = self.get_queryset().filter(unread=True).count() return Response({ 'unread_count': count }, status=status.HTTP_200_OK) @action(methods=['POST'], detail=False, url_path='mark-all-as-read') @method_decorator(ratelimit(key='user', rate='3/s', method='POST', block=True)) def mark_all_as_read(self, request, *args, **kwargs): updated_count = self.get_queryset().update(unread=False) return Response({ 'marked_count': updated_count }, status=status.HTTP_200_OK) @required_params(method='POST', params=['unread']) @method_decorator(ratelimit(key='user', rate='3/s', method='POST', block=True)) def update(self, request, *args, **kwargs): """ user can mark a notification as read/unread, this an update for notification so override update, another method use action @action(method=['POST], detail=True, url_path='mark-as-read') def mark_as_read(self, request, *args, **kwargs) ... @action(method=['POST], detail=True, url_path='mark-as-unread') def mark_as_unread(self, request, *args, **kwargs) ... both are OK. override update is more restful, mark as read/unread can share :param request: :param args: :param kwargs: :return: """ serializer = NotificationSerializerForUpdate( instance=self.get_object(), data=request.data, ) if not serializer.is_valid(): return Response({ 'message': "Please check input", 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) notification = serializer.save() return Response( NotificationSerializer(notification).data, status=status.HTTP_200_OK )
class TweetViewSet(viewsets.GenericViewSet, viewsets.mixins.CreateModelMixin, viewsets.mixins.ListModelMixin): serializer_class = TweetSerializerForCreate pagination_class = EndlessPagination queryset = Tweet.objects.all() def get_permissions(self): if self.action in ['list', 'retrieve']: return [permissions.AllowAny()] return [permissions.IsAuthenticated()] @method_decorator( ratelimit(key='user_or_ip', rate='5/s', method='GET', block=True)) def retrieve(self, request, *args, **kwargs): serializer = TweetSerializerForDetail(self.get_object(), context={'request': request}) return Response(serializer.data) @required_params(params=['user_id']) @method_decorator( ratelimit(key='user_or_ip', rate='5/s', method='GET', block=True)) def list(self, request, *args, **kwargs): user_id = request.query_params['user_id'] cached_tweets = TweetService.get_cached_tweets(user_id) page = self.paginator.paginate_cached_list(cached_tweets, request) if page is None: queryset = Tweet.objects.filter( user_id=user_id).order_by('-created_at') page = self.paginate_queryset(queryset) serializer = TweetSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data) @method_decorator( ratelimit(key='user', rate='1/s', method='POST', block=True)) @method_decorator( ratelimit(key='user', rate='5/m', method='POST', block=True)) def create(self, request, *args, **kwargs): serializer = TweetSerializerForCreate(data=request.data, context={'request': request}) if not serializer.is_valid(): return Response( { 'success': False, 'message': 'Please check input', 'errors': serializer.errors }, status=400) tweet = serializer.save() NewsfeedService.fanout_to_followers(tweet) serializer = TweetSerializer( tweet, context={'request': request}, ) return Response(serializer.data, status=201)
class NotificationViewSet( viewsets.GenericViewSet, viewsets.mixins.ListModelMixin, ): serializer_class = NotificationSerializer permission_classes = (IsAuthenticated, ) filterset_fields = ('unread', ) def get_queryset(self): return self.request.user.notifications.all() @action(methods=['GET'], detail=False, url_path='unread-count') @method_decorator( ratelimit(key='user', rate='3/s', method='GET', block=True)) def unread_count(self, request, *args, **kwargs): count = self.get_queryset().filter(unread=True).count() return Response({'unread_count': count}, status=status.HTTP_200_OK) @action(methods=['POST'], detail=False, url_path='mark-all-as-read') @method_decorator( ratelimit(key='user', rate='3/s', method='POST', block=True)) def mark_all_as_read(self, request, *args, **kwargs): updated_count = self.get_queryset().update(unread=False) return Response({'marked_count': updated_count}, status=status.HTTP_200_OK) @required_params(method='POST', params=['unread']) @method_decorator( ratelimit(key='user', rate='3/s', method='POST', block=True)) def update(self, request, *args, **kwargs): """ 用户可以标记一个 notification 为已读或者未读。标记已读和未读都是对notification 的更新操作,所以直接重载update方法来实现,这种实现方法更加rest。另外一种方法是用一个 专属的action然后用两个方法实现如下 @action(methods=['POST], detail=True, url_path='mark-as-read') def mark_as_read(self, request, *args, **kwargs): ... @action(methods=['POST], detail=True, url_path='mark-as-unread') def mark_as_unread(self, request, *args, **kwargs): ... 但是这种方法无法封装这两个方法公用的逻辑。 """ serializer = NotificationSerializerForUpdate( instance=self.get_object(), data=request.data, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input', 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) notification = serializer.save() return Response( NotificationSerializer(notification).data, status=status.HTTP_200_OK, )
class ProgramSaveForLaterApiView(APIView): """ API VIEW """ @transaction.atomic @method_decorator( ratelimit(key=POST_EMAIL_KEY, rate=settings.SAVE_FOR_LATER_EMAIL_RATE_LIMIT, method='POST')) @method_decorator( ratelimit(key=REAL_IP_KEY, rate=settings.SAVE_FOR_LATER_IP_RATE_LIMIT, method='POST')) def post(self, request): """ **Use Case** * Send favorite program through email to user for later learning. **Example Request for program** POST /api/v1/save/program/ **Example POST Request for program** { "email": "*****@*****.**", "uuid": "587f6abe-bfa4-4125-9fbe-4789bf3f97f1" } """ user = request.user data = request.data program_uuid = data.get('uuid') email = data.get('email') if getattr(request, 'limited', False): return Response({'error_code': 'rate-limited'}, status=403) if get_email_validation_error(email): return Response({'error_code': 'incorrect-email'}, status=400) program = get_programs(uuid=program_uuid) SavedProgram.objects.update_or_create( user_id=user.id, email=email, program_uuid=program_uuid, ) if program: program_data = { 'program': program, 'type': 'program', } if send_email(request, email, program_data): return Response({'result': 'success'}, status=200) else: return Response({'error_code': 'email-not-send'}, status=400) return Response({'error_code': 'program-not-found'}, status=404)
class TestView(View): @method_decorator(ratelimit(key='ip', rate='1/m', method='GET')) def get(self, request): return request.limited @method_decorator(ratelimit(key='ip', rate='1/m', method='POST')) def post(self, request): return request.limited
class LikeViewSet(viewsets.GenericViewSet): queryset = Like.objects.all() serializer_class = LikeSerializerForCreate def get_permissions(self): if self.action in ['create', 'cancel']: return [ IsAuthenticated(), ] return [ AllowAny(), ] @require_params(require_attrs='data', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def create(self, request): serializer = LikeSerializerForCreate( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response({ 'success': False, 'error': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) like, created = serializer.get_or_create() # raise notifications in creation, do not dispatch notifications if liked if created: NotificationService.send_like_notification(like) return Response({ 'success': True, 'like': LikeSerializer(like).data }, status=status.HTTP_200_OK) @action(methods=['POST'], detail=False) @require_params(require_attrs='data', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def cancel(self, request): serializer = LikeSerializerForCancel( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response({ 'success': False, 'error': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) deleted = serializer.cancel() return Response({ 'success': True, 'deleted': deleted, }, status=status.HTTP_200_OK)
class TweetViewSet(viewsets.GenericViewSet): serializer_class = TweetSerializerForCreate queryset = Tweet.objects.all() pagination_class = EndlessPagination def get_permissions(self): if self.action in ['list', 'retrieve']: return [AllowAny()] return [IsAuthenticated()] @required_params(params=['user_id']) def list(self, request, *args, **kwargs): user_id = request.query_params['user_id'] tweets = Tweet.objects.filter(user_id=user_id).prefetch_related('user') cached_tweets = TweetService.get_cached_tweets(user_id) page = self.paginator.paginate_cached_list(cached_tweets, request) if page is None: # select * from twitter_tweets # WHERE user_id = xxx # order by created_at desc # this sql query use user and created_at index together # user only index can't execute this query queryset = Tweet.objects.filter(user_id=user_id).order_by('-created_at') page = self.paginate_queryset(queryset) serializer = TweetSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data) @method_decorator(ratelimit(key='user_or_ip', rate='5/s', method='GET', block=True)) def retrieve(self, request, *args, **kwargs): tweet = self.get_object() serializer = TweetSerializerForDetail( tweet, context={'request': request}, ) return Response(serializer.data) @method_decorator(ratelimit(key='user', rate='1/s', method='POST', block=True)) @method_decorator(ratelimit(key='user', rate='5/m', method='POST', block=True)) def create(self, request): serializer = TweetSerializerForCreate( data = request.data, context={'request': request} ) if not serializer.is_valid(): return Response({ 'success': False, 'message': 'Please check input.', 'errors': serializer.errors, }, status=400 ) tweet = serializer.save() NewsFeedService.fanout_to_followers(tweet) return Response(TweetSerializer(tweet, context={'request':request}).data , status=201)
class TweetViewSet(viewsets.GenericViewSet): serializer_class = TweetSerializerForCreate queryset = Tweet.objects.all() pagination_class = EndlessPagination def get_permissions(self): if self.action in ['list', 'retrieve']: return [AllowAny()] return [IsAuthenticated()] @required_params(params=['user_id']) def list(self, request): user_id = request.query_params['user_id'] tweets = Tweet.objects.filter(user_id=user_id).prefetch_related('user') cached_tweets = TweetService.get_cached_tweets(user_id) page = self.paginator.paginate_cached_list(cached_tweets, request) if page is None: queryset = Tweet.objects.filter(user_id=user_id).order_by('-created_at') page = self.paginate_queryset(queryset) serializer = TweetSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data) @method_decorator(ratelimit(key='user_or_ip', rate='5/s', method='GET', block=True)) def retrieve(self, request, *args, **kwargs): tweet = self.get_object() serializer = TweetSerializerForDetail( tweet, context={'request': request}, ) return Response(serializer.data) @method_decorator(ratelimit(key='user', rate='1/s', method='POST', block=True)) @method_decorator(ratelimit(key='user', rate='5/m', method='POST', block=True)) def create(self, request): serializer = TweetSerializerForCreate( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response({ "success": False, "message": "Input is invalid. Please check it again!", "errors": serializer.errors, }, status=400) # save will call create method in TweetSerializerForCreate tweet = serializer.save() NewsFeedService.fanout_to_followers(tweet) return Response( TweetSerializer(tweet, context={'request': request}).data, status=201, )
class NotificationViewSet( viewsets.GenericViewSet, viewsets.mixins.ListModelMixin, ): serializer_class = NotificationSerializer permission_classes = (IsAuthenticated,) filterset_fields = ('unread',) def get_queryset(self): return self.request.user.notifications.all() # detail is false becaus the current logined user doesn't need to show up in the url @action(methods=['GET'], detail=False, url_path='unread-count') @method_decorator(ratelimit(key='user', rate='3/s', method='GET', block=True)) def unread_count(self, request, *args, **kwargs): # /api/notifications/unread_count/ count = self.get_queryset().filter(unread=True).count() return Response({'unread_count': count}, status=status.HTTP_200_OK) @action(methods=['POST'], detail=False, url_path='mark-all-as-read') @method_decorator(ratelimit(key='user', rate='3/s', method='POST', block=True)) def mark_all_as_read(self, request, *args, **kwargs): updated_count = self.get_queryset().update(unread=False) return Response({'marked_count': updated_count}, status=status.HTTP_200_OK) @required_params(method='POST', params=['unread']) @method_decorator(ratelimit(key='user', rate='3/s', method='POST', block=True)) def update(self, request, *args, **kwargs): """ 用户可以标记一个 notification 为已读或者未读。标记已读和未读都是对 notification 的一次更新操作,所以直接重载 update 的方法来实现。另外一种实现方法是用一个专属的 action: @action(methods=['POST'], detail=True, url_path='mark-as-read') def mark_as_read(self, request, *args, **kwargs): ... @action(methods=['POST'], detail=True, url_path='mark-as-unread') def mark_as_unread(self, request, *args, **kwargs): ... 两种方法都可以,我更偏好重载 update,因为更通用更 rest 一些, 而且 mark as unread 和 mark as read 可以公用一套逻辑。 """ serializer = NotificationSerializerForUpdate( instance=self.get_object(), data=request.data, ) if not serializer.is_valid(): return Response({ 'message': "Please check input", 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) notification = serializer.save() return Response( NotificationSerializer(notification).data, status=status.HTTP_200_OK, )
class RateLimitedAdmin(admin.AdminSite): @method_decorator(never_cache) @method_decorator( ratelimit(key='ip', rate='3/m', method=ratelimit.UNSAFE, block=True)) @method_decorator( ratelimit(key='post:username', rate='3/m', method=ratelimit.UNSAFE, block=True)) def login(self, request, extra_context=None): return super(RateLimitedAdmin, self).login(request, extra_context)
class LikeViewSet(viewsets.GenericViewSet): queryset = Like.objects.all() permission_classes = [IsAuthenticated] serializer_class = LikeSerializerForCreate @required_params(method='POST', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def create(self, request, *args, **kwargs): serializer = LikeSerializerForCreate( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input.', 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) instance, created = serializer.get_or_create() # 发送 like notification if created: NotificationService.send_like_notification(instance) return Response( LikeSerializer(instance).data, status=status.HTTP_201_CREATED, ) @action(methods=['POST'], detail=False) @required_params(method='POST', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def cancel(self, request, *args, **kwargs): serializer = LikeSerializerForCancel( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input.', 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) deleted_count = serializer.cancel() return Response({ 'success': True, 'deleted': deleted_count, }, status=status.HTTP_200_OK)
class LikeViewSet(viewsets.GenericViewSet): queryset = Like.objects.all() permission_classes = [IsAuthenticated] serializer_class = LikeSerializerForCreate @required_params(method='POST', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def create(self, request, *args, **kwargs): serializer = LikeSerializerForCreate( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input', 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) instance, created = serializer.get_or_create() if created: NotificationService.send_like_notification(instance) return Response( LikeSerializer(instance).data, status=status.HTTP_201_CREATED, ) # 设计取消like的api时候不要用 httpdelete /api/likes/{like_object_id} # 因为如果在前端一个用户点击了like,又很快的在点击一下取消like,如果用上述的delete的方式,url需要知道like的object id # 这就意味着如果用户取消点赞必须要等到得到了这个点赞的id,这种体验不好 视频 第二十章 like 1_2_3的43:19 # 所以取消点赞的api是 POST /api/likes/cancle @action(methods=['POST'], detail=False) @required_params(method='POST', params=['content_type', 'object_id']) @method_decorator( ratelimit(key='user', rate='10/s', method='POST', block=True)) def cancel(self, request, *args, **kwargs): serializer = LikeSerializerForCancel( data=request.data, context={'request': request}, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input', 'errors': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) serializer.cancel() return Response({'success': True}, status=status.HTTP_200_OK)
class NotificationViewSet( viewsets.GenericViewSet, viewsets.mixins.ListModelMixin, ): serializer_class = NotificationSerializer permission_classes = (IsAuthenticated, ) def get_queryset(self): return Notification.objects.filter(recipient=self.request.user) @action(methods=['GET'], detail=False, url_path='unread-count') @method_decorator( ratelimit(key='user', rate='3/s', method='GET', block=True)) def unread_count(self, request, *args, **kwargs): # GET /api/notifications/unread-count/ count = self.get_queryset().filter(unread=True).count() return Response({'unread_count': count}, status=status.HTTP_200_OK) @action(methods=['POST'], detail=False, url_path='mark-all-as-read') @method_decorator( ratelimit(key='user', rate='3/s', method='POST', block=True)) def mark_all_as_read(self, request, *args, **kwargs): updated_count = self.get_queryset().update(unread=False) return Response({'marked_count': updated_count}, status=status.HTTP_200_OK) @required_params(method='POST', params=['unread']) @method_decorator( ratelimit(key='user', rate='3/s', method='POST', block=True)) def update(self, request, *args, **kwargs): # PUT /api/notifications/1 (request data unread is True or False) serializer = NotificationSerializerForUpdate( instance=self.get_object(), data=request.data, ) if not serializer.is_valid(): return Response( { 'message': 'Please check input', 'error': serializer.errors, }, status=status.HTTP_400_BAD_REQUEST) notification = serializer.save() return Response( NotificationSerializer(notification).data, status=status.HTTP_200_OK, )
class UserConsentActionView(DetailView): """ An abstract view Validates that a token is valid for consent ID + email_hash """ model = models.UserConsent context_object_name = "consent" token_salt = consent_settings.UNSUBSCRIBE_SALT @method_decorator(ratelimit(key="ip", rate=RATELIMIT)) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def action(self, consent): raise NotImplementedError("blah") def get_object(self, queryset=None): consent = super().get_object(queryset) token = self.kwargs.get("token") if utils.validate_token(token, consent, salt=self.token_salt): self.action(consent) return consent else: raise Http404("This does not work") def get_context_data(self, **kwargs): c = super().get_context_data(**kwargs) c["token"] = utils.get_consent_token(c["consent"], salt=self.token_salt) return c
def view_with_rate_limit(request, *args, **kwargs): # cannot use @decorator syntax because reading from settings during import time # prevents django.test.override_settings from working as expected. # that's why I'm manually decorating the view in this custom view return ratelimit(key=ratelimit_key, rate=settings.RATELIMIT_RATE, block=settings.RATELIMIT_ENABLE)(view)( request, *args, **kwargs )
class RegistrationAccepted(UpdateView): template_name = "registration/accepted.html" model = models.Registration context_object_name = "registration" @method_decorator(ratelimit(key="ip", rate="100/h")) def dispatch(self, request, *args, **kwargs): self.access_code = kwargs["access_code"] return UpdateView.dispatch(self, request, *args, **kwargs) def get_object(self): return UpdateView.get_queryset(self).get( access_code=self.access_code, accepted=True, ) def get_success_url(self): return reverse( "registration:confirmation-done", kwargs={"access_code": self.access_code}, ) def get_form_class(self): if self.object.scholarship and not self.object.scholarship_confirmed: return forms.RegistrationAcceptedScholarshipForm if self.object.scholarship_confirmed: return forms.RegistrationAcceptedScholarshipConfirmedForm if self.object.confirmed: return forms.RegistrationAcceptedConfirmedForm return forms.RegistrationAcceptedForm
class NewsFeedViewSet(viewsets.GenericViewSet): pagination_class = EndlessPagination def get_permissions(self): if self.action == 'list': return [ IsAuthenticated(), ] return [ AllowAny(), ] @method_decorator( ratelimit(key='user', rate='5/s', method='GET', block=True)) def list(self, request): cached_newsfeeds = NewsFeedService.load_newsfeeds_through_cache( user_id=request.user.id) page = self.paginator.paginate_cached_list(cached_newsfeeds, request) # cache not enough, access the db directly for extra if not page: newsfeeds = NewsFeed.objects.filter(user_id=request.user.id) page = self.paginate_queryset(newsfeeds) serializer = NewsFeedSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data)
class NewsFeedViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] pagination_class = EndlessPagination def get_queryset(self): # 自定义 queryset,因为 newsfeed 的查看是有权限的 # 只能看 user=当前登录用户的 newsfeed # 也可以是 self.request.user.newsfeed_set.all() # 但是一般最好还是按照 NewsFeed.objects.filter 的方式写,更清晰直观 return NewsFeed.objects.filter(user=self.request.user) @method_decorator( ratelimit(key='user', rate='5/s', method='GET', block=True)) def list(self, request): cached_newsfeeds = NewsFeedService.get_cached_newsfeeds( request.user.id) page = self.paginator.paginate_cached_list(cached_newsfeeds, request) if page is None: queryset = NewsFeed.objects.filter(user=request.user) page = self.paginate_queryset(queryset) serializer = NewsFeedSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data)
class NewsFeedViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] pagination_class = EndlessPagination @method_decorator( ratelimit(key='user', rate='5/s', method='GET', block=True)) def list(self, request): cached_newsfeeds = NewsFeedService.get_cached_newsfeeds( request.user.id) page = self.paginator.paginate_cached_list(cached_newsfeeds, request) if page is None: if GateKeeper.is_switch_on('switch_newsfeed_to_hbase'): page = self.paginator.paginate_hbase(HBaseNewsFeed, (request.user.id, ), request) else: queryset = NewsFeed.objects.filter(user=request.user) page = self.paginate_queryset(queryset) serializer = NewsFeedSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data)
def test_wrap_view(self): class TestView(View): def get(self, request): return request.limited view = TestView.as_view() wrapped = ratelimit(key='ip', rate='1/m', block=False)(view) assert not wrapped(rf.get('/')) assert wrapped(rf.get('/'))
def test_class_decorator(self): @method_decorator(ratelimit(key='ip', rate='1/m', block=False), name='get') class TestView(View): def get(self, request): return request.limited view = TestView.as_view() assert not view(rf.get('/')) assert view(rf.get('/'))
class RegistrationWeek1AcceptedConfirm(DetailView): template_name = "registration/week1_accepted_confirmed.html" model = models.RegistrationWeek1 context_object_name = "registration" @method_decorator(ratelimit(key="ip", rate="100/h")) def dispatch(self, request, *args, **kwargs): self.access_code = kwargs["access_code"] return DetailView.dispatch(self, request, *args, **kwargs) def get_object(self): return UpdateView.get_queryset(self).get(access_code=self.access_code)
def test_ratelimit_no_method_match(self, mock_count): #we're testing to make sure that the method type doesn't match #the default POST self.request.method = "GET" #we dont need any parameters since #we're only testing that the rate limited doesn't get called wrapper = decorators.ratelimit() mock_view = Mock() mock_view.__name__ = "my mock view" wrapped_mock = wrapper(mock_view) wrapped_mock(self.request) self.assertFalse(mock_count.called)
class VerifyVerificationCodeAPIView(GenericAPIView): serializer_class = serializers.CheckVerificationCodeSerializer permission_classes = [SignatureCheckPermission] authentication_classes = [] def get_email(self, uidb64, token): try: uid = force_text(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) except (TypeError, ValueError, OverflowError, User.DoesNotExist): user = None if user is not None and helpers.verification_token.check_token(user, token): return user.email def get_object(self): uidb64 = self.request.data.get('uidb64') token = self.request.data.get('token') if uidb64 and token: email = self.get_email(uidb64, token) else: email = self.request.data.get('email') return get_object_or_404(models.VerificationCode.objects.all(), user__email__iexact=email) @method_decorator(ratelimit(key='post:email', rate='12/m', method='POST', block=True)) def post(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance, data=request.data) serializer.is_valid(raise_exception=True) serializer.save(date_verified=now()) EmailAddress.objects.update_or_create( user=instance.user, email=instance.user.email, defaults={'verified': True, 'primary': True} ) login( request=self.request, user=instance.user, backend='django.contrib.auth.backends.ModelBackend', ) return Response({'email': instance.user.email})
class ConsentCreateView(CreateView): """ The view isn't part of urls.py but you can add it to your own project's url configuration if you want to use it. To mount it, add a source_id kwarg, for instance:: path("signup/<int:source_id>/", SignupView.as_view()), """ model = models.UserConsent form_class = forms.EmailConsentForm template_name = "consent/user/create.html" @method_decorator(ratelimit(key="ip", rate=RATELIMIT)) def dispatch(self, request, *args, **kwargs): self.consent_source = get_object_or_404(models.ConsentSource, id=kwargs["source_id"]) return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["consent_source"] = self.consent_source return kwargs def get_context_data(self, **kwargs): c = super().get_context_data(**kwargs) c["consent_source"] = self.consent_source return c def form_valid(self, form): ret_value = super().form_valid(form) consent = self.object consent.email_confirmation(request=self.request) return ret_value def get_success_url(self): """ This requires a project with a urlconf specifying a view with the name 'signup_confirmation'. """ return reverse("signup_confirmation", kwargs={"source_id": self.consent_source.id})
class CommentCreate(LoginRequiredMixin, CreateView): model = Comment context_object_name = 'comments' http_method_names = ['post'] template_name = 'products/includes/comments.html' fields = ['content'] def post(self, request, *args, **kwargs): if request.is_ajax(): text = request.POST['content'] sku_id = int(self.kwargs['sku_id']) self.create_comment(text, sku_id) html = render_to_string(self.template_name, self.get_context_data()) return HttpResponse(html) raise Http404() def create_comment(self, text, sku_id): user_profile = UserProfile.objects.only('id').get( user__id=self.request.user.id) self.sku = SKU.objects.get(id=sku_id) self.sku.comments.create(owner=user_profile, content=text) def get_context_data(self, **kwargs): context = {self.context_object_name: self.get_queryset()} return context def get_queryset(self): only_fields = ['owner__user__username'] queryset = self.sku.comments.select_related().only( *only_fields).order_by('-time') return queryset def http_method_not_allowed(self, request, *args, **kwargs): raise Http404() @method_decorator(ratelimit(key='user', rate='5/m', block=True)) def dispatch(self, request, *args, **kwargs): return super(CommentCreate, self).dispatch(request, *args, **kwargs)
class NewsfeedViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] pagination_class = EndlessPagination @method_decorator( ratelimit(key='user', rate='5/s', method='GET', block=True)) def list(self, request): newsfeeds = NewsfeedService.get_cached_newsfeeds(request.user.id) page = self.paginator.paginate_cached_list(newsfeeds, request) # The data is not in the cache when the data is not recent. # Only cache the recent REDIS_LIST_LENGTH_LIMIT objects in cache. if page is None: queryset = Newsfeed.objects.filter( user_id=request.user.id).order_by('-created_at') page = self.paginate_queryset(queryset) serializer = NewsfeedSerializer(page, context={'request': request}, many=True) return self.get_paginated_response(serializer.data)
def test_ratelimit_decorator(self): #this tests the normal behavior of the ratelimit #where we call it twice and one gets limited wrapper = decorators.ratelimit( limit_by=decorators.ATTR, field='oauth_consumer_key', #we'll pick a low rate so we can test rate="1/h" ) mock_view = Mock() #this is need for the functools methods mock_view.__name__ = "my mock view" wrapped_mock = wrapper(mock_view) #call the wrapped mock view for the first time wrapped_mock(self.request) self.assertFalse(self.request.limited) self.assertEqual(self.request.rate_limit_count, 1) #call the wrapped mock view for the second time should #result in the request being limited wrapped_mock(self.request) self.assertTrue(self.request.limited) self.assertEqual(self.request.rate_limit_count, 2)
class RegistrationWeek1Accepted(UpdateView): template_name = "registration/week1_accepted.html" model = models.RegistrationWeek1 context_object_name = "registration" @method_decorator(ratelimit(key="ip", rate="100/h")) def dispatch(self, request, *args, **kwargs): self.access_code = kwargs["access_code"] return UpdateView.dispatch(self, request, *args, **kwargs) def get_object(self): return UpdateView.get_queryset(self).get( access_code=self.access_code, accepted=True, ) def get_success_url(self): return reverse( "registration:week1-confirmation-done", kwargs={"access_code": self.access_code}, ) def get_form_class(self): return forms.RegistrationWeek1AcceptedForm
class NewsFeedViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] pagination_class = EndlessPagination def get_queryset(self): # instead of using queryset() directly, we need to customize the method # since NewsFeed is only available to authenticated users return NewsFeed.objects.filter(user=self.request.user) @method_decorator( ratelimit(key='user', rate='5/s', method='GET', block=True)) def list(self, request): cached_newsfeeds = NewsFeedService.get_cached_newsfeeds( request.user.id) page = self.paginator.paginate_cached_list(cached_newsfeeds, request) if page is None: queryset = NewsFeed.objects.filter(user=request.user) page = self.paginate_queryset(queryset) serializer = NewsFeedSerializer( page, context={'request': request}, many=True, ) return self.get_paginated_response(serializer.data)
def dispatch(self, *args, **kwargs): return ratelimit( **self.get_ratelimit_config() )(super(RateLimitMixin, self).dispatch)(*args, **kwargs)
elif not window.started(): template_name = 'ctflex/board/waiting.html' elif window.ended(): template_name = 'ctflex/board/ended.html' else: template_name = 'ctflex/board/current.html' return render(request, template_name, context) # endregion # region Auth password_change = limited_http_methods('GET', 'POST')( ratelimit(key='user', method='POST', rate='4/m', block=True)( auth_views.password_change)) password_reset = limited_http_methods('GET', 'POST')( ratelimit(method='POST', key='ip', rate='4/m', block=True)( auth_views.password_reset)) @limited_http_methods('GET') def logout_done(request, *, message="You have been logged out.", redirect_url='ctflex:index'): messages.success(request, message) return redirect(redirect_url) @limited_http_methods('GET')
from ratelimit.decorators import ratelimit from ratelimit.exceptions import Ratelimited # See: https://www.bing.com/webmaster/help/how-to-verify-bingbot-3905dc26 # and: https://support.google.com/webmasters/answer/80553?hl=en APPROVED_DOMAINS = [ 'google.com', 'googlebot.com', 'search.msn.com', 'localhost', # For dev. ] ratelimiter = ratelimit(key='ip', rate='250/h', block=True) def ratelimit_if_not_whitelisted(view): """A wrapper for the ratelimit function that adds a whitelist for approved crawlers. """ ratelimited_view = ratelimiter(view) @functools.wraps(view) def wrapper(request, *args, **kwargs): try: return ratelimited_view(request, *args, **kwargs) except Ratelimited as e: if is_whitelisted(request): return view(request, *args, **kwargs)
django_admin_blocks.autodiscover() urlpatterns = [ url(r'^$', HomeView.as_view(), name='home'), url(r'^browse/', include('hub.apps.browse.urls', namespace='browse')), url(r'^submit-resource/', include('hub.apps.submit.urls', namespace='submit')), url(r'^api/v1/', include('hub.apps.api.urls', namespace='api')), # url(r'^login/$', login, name='login'), url(r'^login/$', ratelimit( key='post:username', rate=settings.LOGIN_RATE_LIMIT, block=True)(login), name='login'), url(r'^logout/$', logout, name='logout'), url(r'^_ad/', include(admin.site.urls)), # Link checking url pattern url(r'^_ad/linkcheck/', include('linkcheck.urls')), url(r'^s3direct/', include('s3direct.urls')), # Let's Encrypt (django-acme-challenge) url(r'^.well-known/acme-challenge/', include('acme_challenge.urls')), url(r'^', include('django.contrib.flatpages.urls')), ]
def dispatch(self, *args, **kwargs): return ratelimit(rate='5/m')(super(ImageCreateView, self).dispatch)(*args, **kwargs)
grouped_filters = [] for group_options, filters in filter_groups.items(): group_name, group_slug, group_order = group_options sorted_filters = sorted(filters, key=attrgetter('name')) grouped_filters.append(FilterGroup(name=group_name, slug=group_slug, order=group_order, options=sorted_filters)) sorted_filters = sorted(grouped_filters, key=attrgetter('order'), reverse=True) return FacetedFilterSerializer(sorted_filters, many=True).data # Since the search endpoint accepts user input (via query parameters) and its # response is compressed, use rate limiting to mitigate the BREACH attack # (see http://breachattack.com/). It still needs to allow a user to click # the filter switches (bug 1426968). # Alternate: forbid gzip by setting Content-Encoding: identity search = never_cache(ratelimit(key='user_or_ip', rate='25/m', block=True)( SearchView.as_view() )) @shared_cache_control(s_maxage=60 * 60 * 24 * 7) def plugin(request): """Render an OpenSearch Plugin.""" return render(request, 'search/plugin.html', { 'locale': request.LANGUAGE_CODE }, content_type='application/opensearchdescription+xml')
logger = logging.getLogger("posts.views") def ratelimited(request, *args, **kwargs): logger.info("Rate limit reached for request", request.META["CLIENT_IP"]) return HttpResponse(status=429, content="Too many requests, please slow down!") GLOBAL_REQUEST_GROUP = "global" limit = ratelimit( key="header:client-ip", method="GET", rate="5/s", block=True, group=GLOBAL_REQUEST_GROUP, ) class Index(View): @method_decorator(limit) def get(self, request, **kwargs): previous_id = request.GET.get("p") post = self._random_post(previous_id=previous_id) if post is None: logger.debug("Exhausted all posts") messages.error(request, "No more posts to view, submit your own!") return redirect("submit") context = {"post": post}
from oauth2_provider.models import get_application_model from oauth2_provider.exceptions import OAuthToolkitError from oauth2_provider.http import HttpResponseUriRedirect from ratelimit.decorators import ratelimit from ..forms import AllowForm, SimpleAllowForm from ..models import ExpiresIn from oauth2_provider.forms import AllowForm as DotAllowForm from django.conf import settings logger = logging.getLogger('hhs_server.%s' % __name__) __author__ = "Alan Viars" @method_decorator(ratelimit(key='user_or_ip', rate='5/m', method=['GET', 'POST'], block=True), name='dispatch') class AuthorizationView(DotAuthorizationView): """ Override the base authorization view from dot to use the custom AllowForm. """ form_class = SimpleAllowForm login_url = '/accounts/oauth2-login' template_name = getattr(settings, 'AUTHORIZATION_TEMPLATE_NAME', "design_system/authorize.html") @method_decorator(ratelimit(key='user_or_ip', rate='5/m',
"""Route URLs to apps""" from django.conf import settings from django.conf.urls import include, url from django.contrib import admin from ratelimit.decorators import ratelimit admin.autodiscover() admin.site.login = ( ratelimit(key='ip', rate='1/s', method='POST', block=True)( ratelimit(key='ip', rate='10/h', method='POST', block=True)( admin.site.login))) urlpatterns = [ url(r'^{}/'.format(settings.ADMIN_URL_PATH), include(admin.site.urls), name='admin'), url(r'', include('ctflex.urls')), url(r'', include('pactf_web.urls')), ]