from django.db import models from django.shortcuts import render_to_response import datetime, random class RequestRateManager(models.Manager): def clean_expired(self): """Purges old entries from database. If you use max_value's greater than 600 (seconds, ten minutes), change the following line.""" l_time = datetime.datetime.now() - datetime.timedelta(seconds = 600) self.get_query_set().filter(last_update__lt=l_time).delete() class RequestRate(models.Model): """Implements a rate limit per IP. value is increased at every request by the amount the request specifies, which is meant to be the average number of seconds until the same request could be made again. value decreases at a rate of 1 per second until it is a 0. """ name = models.CharField(max_length=20) ipaddr = models.IPAddressField(blank=True, null=True) last_update = models.DateTimeField() value = models.FloatField() objects = RequestRateManager() def update(self): this_update = datetime.datetime.now() td = this_update - self.last_update time_delta_sec = (float(td.days) * 3600.0 * 60.0 + float(td.seconds) + float(td.microseconds) / 1000000.0) self.value -= time_delta_sec if self.value < 0: self.value = 0 self.last_update = this_update def request(self, seconds, max_value): self.update() if self.value + seconds < max_value: self.value += seconds self.save() return True else: self.save() return False def limit(name, seconds, max_value, per_ip=True, limit_exceeded_view=None, limit_exceeded_template='connection_limit_exceeded.html'): """Makes a rate-limiting decorator""" def default_limit_exceeded_view(*args, **kwargs): return render_to_response(limit_exceeded_template) limit_exceeded_view = limit_exceeded_view or default_limit_exceeded_view def decorator(view): def limited_view(request, *args, **kwargs): if random.random() < 0.05: RequestRate.objects.clean_expired() if per_ip: ipaddr = request.META['REMOTE_ADDR'] else: ipaddr = None (l,tmp) = RequestRate.objects.get_or_create(name=name, ipaddr=ipaddr, defaults={ 'last_update': datetime.datetime.now(), 'value': 0 }) if l.request(seconds, max_value): return view(request, *args, **kwargs) else: return limit_exceeded_view(*args, **kwargs) return limited_view return decorator