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