Implemented IP authentication

libraries get associated with a library_block table (IP ranges)
moved superlogin here
IP stuff mostly copied from
https://github.com/benliles/django-ipauth/blob/master/ipauth/models.py
The model is that you need to be in an allowed IP range to "join" a
library. Once you've joined, you can use your login from anywhere. to
use that library.
Asign IP ranges in admin
pull/1/head
eric 2013-10-08 15:37:22 -04:00
parent 2f703a7815
commit 439169a1ab
20 changed files with 571 additions and 57 deletions

View File

@ -41,8 +41,8 @@ from regluit.core.lookups import (
OwnerLookup,
EditionLookup
)
from regluit.libraryauth.models import Library
from regluit.libraryauth.admin import LibraryAdmin
from regluit.libraryauth.models import Library, Block
from regluit.libraryauth.admin import LibraryAdmin, BlockAdmin
class RegluitAdmin(AdminSite):
login_template = 'registration/login.html'
@ -214,6 +214,7 @@ admin_site = RegluitAdmin("Admin")
admin_site.register(User, UserAdmin)
admin_site.register(Library, LibraryAdmin)
admin_site.register(Block, BlockAdmin)
admin_site.register(models.Work, WorkAdmin)
admin_site.register(models.Claim, ClaimAdmin)
admin_site.register(models.RightsHolder, RightsHolderAdmin)

View File

@ -13,7 +13,6 @@ from django import forms
from django.conf import settings
from django.conf.global_settings import LANGUAGES
from django.contrib.auth.models import User
from django.contrib.auth.forms import AuthenticationForm
from django.core.validators import validate_email
from django.db import models
from django.forms.widgets import RadioSelect
@ -622,14 +621,6 @@ class FeedbackForm(forms.Form):
return cleaned_data
class AuthForm(AuthenticationForm):
def __init__(self, request=None, *args, **kwargs):
if request and request.method == 'GET':
saved_un= request.COOKIES.get('un', None)
super(AuthForm, self).__init__(initial={"username":saved_un},*args, **kwargs)
else:
super(AuthForm, self).__init__(*args, **kwargs)
class MsgForm(forms.Form):
msg = forms.CharField(widget=forms.Textarea(), error_messages={'required': 'Please specify a message.'})

View File

@ -84,9 +84,9 @@ function highlightTarget(targetdiv) {
{% with supporter.profile.tagline as tagline %}{% if tagline %}{{ tagline }}{% else %} {% endif %}{% endwith %}
</span>
{% if supporter.library.group in request.user.groups.all %}
<i>This is MY Library!</i>
<i>This is {{ request.user }}'s Library!</i>
{% else %}
<a href="{% url join_library supporter.username %}" class="fakeinput">Make this my Library</a>
{% include supporter.library.join_template %}
{% endif %}
</div>

View File

@ -73,7 +73,7 @@ function highlightTarget(targetdiv) {
{% block topsection %}
<div id="locationhash">{{ activetab }}</div>
{% if supporter.library %}
<div class="launch_top pale">{{ supporter.username }} is a Library participating in Unglue.it. <a href="{% url library supporter.username %}">Click here</a> to use {{ supporter.username }}'s books.
<div class="launch_top pale">{{ supporter.username }} is a Library participating in Unglue.it. <a href="{% url join_library supporter.username %}">Click here</a> to use {{ supporter.username }}'s books.
</div>
{% endif %}

View File

@ -49,9 +49,7 @@ urlpatterns = patterns(
url(r"^supporter/(?P<supporter_username>[^/]+)/$", "supporter", {'template_name': 'supporter.html'}, name="supporter"),
url(r"^supporter/(?P<userlist>[^/]+)/marc/$", "marc", name="user_marc"),
url(r"^library/(?P<supporter_username>[^/]+)/$", "supporter", {'template_name': 'library.html'}, name="library"),
url(r"^library/(?P<library>[^/]+)/join/$", "join_library", name="join_library"),
url(r"^accounts/manage/$", login_required(ManageAccount.as_view()), name="manage_account"),
url(r'^accounts/superlogin/$', 'superlogin', name='superlogin'),
url(r"^search/$", "search", name="search"),
url(r"^privacy/$", TemplateView.as_view(template_name="privacy.html"),
name="privacy"),

View File

@ -107,7 +107,6 @@ from regluit.frontend.forms import (
WorkForm,
OtherWorkForm,
MsgForm,
AuthForm,
PressForm,
KindleEmailForm,
MARCUngluifyForm,
@ -136,6 +135,7 @@ from regluit.payment.parameters import (
from regluit.utils.localdatetime import now, date_today
from regluit.booxtream.exceptions import BooXtreamError
from regluit.libraryauth.views import Authenticator
from regluit.libraryauth.models import Library
logger = logging.getLogger(__name__)
@ -297,18 +297,6 @@ def stub(request):
def acks(request, work):
return render(request,'front_matter.html', {'campaign': work.last_campaign()})
def superlogin(request, **kwargs):
extra_context = None
if request.method == 'POST' and request.user.is_anonymous():
username=request.POST.get("username", "")
try:
user=models.User.objects.get(username=username)
extra_context={"socials":user.profile.social_auths}
except:
pass
if request.GET.has_key("add"):
request.session["add_wishlist"]=request.GET["add"]
return login(request, extra_context=extra_context, authentication_form=AuthForm, **kwargs)
@login_required
def social_auth_reset_password(request):
@ -316,15 +304,6 @@ def social_auth_reset_password(request):
request.user.set_password('%010x' % random.randrange(16**10))
request.user.save()
return password_reset(request)
def join_library(request, library):
library=get_object_or_404(Library, user__username=library)
if library.authenticate(request.user):
request.user.groups.add(library.group)
return HttpResponseRedirect(reverse('library',args=[str(library)]))
else:
return library.authenticator(request)
return render(request, 'join_library.html', {'library': library})
def work(request, work_id, action='display'):
work = safe_get_work(work_id)
@ -1871,6 +1850,10 @@ def supporter(request, supporter_username, template_name):
librarything_id = None
process_kindle_email(request)
try:
authenticator = Authenticator(request,supporter.library)
except Library.DoesNotExist:
authenticator=None
context = {
"supporter": supporter,
@ -1888,7 +1871,8 @@ def supporter(request, supporter_username, template_name):
"goodreads_auth_url": reverse('goodreads_auth'),
"goodreads_id": goodreads_id,
"librarything_id": librarything_id,
"activetab": activetab
"activetab": activetab,
"authenticator": authenticator
}
return render(request, template_name, context)

View File

@ -1,5 +1,37 @@
'''import logging
from django.conf import settings
from django.http import HttpResponseRedirect
from .views import superlogin
from . import backends
def authenticate(user,library):
backend= getattr(backends, library.backend + '_authenticate')
return backend(user, library)
logger = logging.getLogger(__name__)
class Authenticator:
request=None
library=None
def __init__(self, request, library):
self.request=request
self.library=library
def process(self, success_url, deny_url):
logger.info('authenticator for %s at %s.'%(self.request.user, self.library))
if self.library.has_user(self.request.user):
return HttpResponseRedirect(success_url)
backend_test= getattr(backends, self.library.backend + '_authenticate')
if backend_test(self.request, self.library):
if self.request.user.is_authenticated():
self.library.add_user(self.request.user)
return HttpResponseRedirect(success_url)
else:
return superlogin(self.request, extra_context={'library':self.library}, template_name='libraryauth/library_login.html')
else:
backend_authenticator= getattr(backends, self.library.backend + '_authenticator')
return backend_authenticator(self.request, self.library, success_url, deny_url)
def allowed(self):
backend_test= getattr(backends, self.library.backend + '_authenticate')
return backend_test(self.request, self.library)
'''

View File

@ -28,4 +28,7 @@ class LibraryAdmin(ModelAdmin):
form = LibraryAdminForm
search_fields = ['user__username']
class BlockAdmin(ModelAdmin):
list_display = ('library', 'lower', 'upper',)
search_fields = ('library__user__username', 'lower', 'upper',)

View File

@ -1,6 +1,32 @@
import logging
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import render
def IP_authenticate(user, library):
return True
from .models import Block, IP
logger = logging.getLogger(__name__)
def ip_authenticate(request, library):
try:
ip = IP(request.META['REMOTE_ADDR'])
print str(ip)
blocks = Block.objects.filter(Q(lower=ip) | Q(lower__lte=ip, upper__gte=ip))
for block in blocks:
if block.library==library:
logger.info('%s authenticated for %s from %s'%(request.user, library, ip))
return True
return False
except KeyError:
return False
def ip_authenticator(request, library, success_url, deny_url):
return HttpResponseRedirect(deny_url)
def cardnum_authenticate(user, library):
def cardnum_authenticate(request, library):
# test params
return True
def cardnum_authenticator(request, library, success_url, deny_url):
#send a form
return render(request, 'cardnum.html', context)

10
libraryauth/forms.py Normal file
View File

@ -0,0 +1,10 @@
from django.contrib.auth.forms import AuthenticationForm
class AuthForm(AuthenticationForm):
def __init__(self, request=None, *args, **kwargs):
if request and request.method == 'GET':
saved_un= request.COOKIES.get('un', None)
super(AuthForm, self).__init__(initial={"username":saved_un},*args, **kwargs)
else:
super(AuthForm, self).__init__(*args, **kwargs)

183
libraryauth/ip/models.py Normal file
View File

@ -0,0 +1,183 @@
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models
from django.db.models import Q
from django.forms import IPAddressField as BaseIPAddressField
from django.utils.translation import ugettext_lazy as _
def ip_to_long(value):
validators.validate_ipv4_address(value)
lower_validator = validators.MinValueValidator(0)
upper_validator = validators.MinValueValidator(255)
value = value.split('.')
output = 0
for i in range(0, 4):
validators.validate_integer(value[i])
lower_validator(value[i])
upper_validator(value[i])
output += int(value[i]) * (256**(3-i))
return output
def long_to_ip(value):
validators.validate_integer(value)
value = int(value)
validators.MinValueValidator(0)(value)
validators.MaxValueValidator(4294967295)(value)
return '%d.%d.%d.%d' % (value >> 24, value >> 16 & 255,
value >> 8 & 255, value & 255)
class IP(object):
def __init__(self, value):
self.int = value
def _set_int(self, value):
if isinstance(value, IP):
self._int = IP.int
try:
self._int = int(value)
except ValueError:
self._int = ip_to_long(value)
except (TypeError, ValidationError):
self._int = None
def _get_int(self):
return self._int
int = property(_get_int, _set_int)
def _get_str(self):
if self.int:
return long_to_ip(self.int)
return ''
string = property(_get_str, _set_int)
def __eq__(self, other):
if not isinstance(other, IP):
try:
other = IP(other)
except:
return False
return self.int == other.int
def __cmp__(self, other):
if not isinstance(other, IP):
other = IP(other)
if self.int and other.int:
return self.int.__cmp__(other.int)
raise ValueError('Invalid arguments')
def __unicode__(self):
return self.string
def __str__(self):
return self.string
class IPAddressFormField(BaseIPAddressField):
default_validators = []
def prepare_value(self, value):
if isinstance(value, IP):
return value.string
try:
return IP(value).string
except:
pass
return value
def to_python(self, value):
if value in validators.EMPTY_VALUES:
return None
try:
return IP(value)
except ValidationError:
raise ValidationError(self.default_error_messages['invalid'],
code='invalid')
class IPAddressModelField(models.IPAddressField):
__metaclass__ = models.SubfieldBase
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
models.Field.__init__(self, *args, **kwargs)
def get_internal_type(self):
return "PositiveIntegerField"
def get_prep_value(self, value):
if not value:
return value
if isinstance(value, IP):
return value.int
def to_python(self, value):
if isinstance(value, IP):
return value
try:
return IP(value)
except ValidationError:
return None
def formfield(self, **kwargs):
defaults = {'form_class': IPAddressFormField}
defaults.update(kwargs)
return super(models.IPAddressField, self).formfield(**defaults)
class Range(models.Model):
user = models.ForeignKey(User, related_name='+')
lower = IPAddressModelField(db_index=True, unique=True)
upper = IPAddressModelField(db_index=True, blank=True, null=True)
def clean(self):
if self.upper and self.upper.int:
try:
if self.lower >= self.upper:
raise ValidationError('Lower end of the range must be less '
'than the upper end')
except ValueError, e:
pass
others = Range.objects.exclude(pk=self.pk)
query = Q(lower__lte=self.lower, upper__gte=self.lower) | \
Q(lower=self.lower)
if self.upper and self.upper.int:
textual = u'%s-%s' % (self.lower, self.upper)
query = query | Q(lower__range=(self.lower, self.upper)) | \
Q(lower__lte=self.upper, upper__gte=self.upper)
else:
textual = str(self.lower)
query = others.filter(query)
if query.exists():
values = query.distinct().values_list('user__username', flat=True)
raise ValidationError('%s overlaps a range in in use by: %s' % (textual,
', '.join(list(frozenset(values))[:5])))
def __unicode__(self):
if self.upper and self.upper.int:
return u'%s %s-%s' % (self.user.get_full_name(), self.lower,
self.upper)
return u'%s %s' % (self.user.get_full_name(), self.lower)
class Meta:
ordering = ['lower',]

View File

@ -12,15 +12,27 @@ class Migration(SchemaMigration):
db.create_table('libraryauth_library', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='library', unique=True, to=orm['auth.User'])),
('backend', self.gf('django.db.models.fields.CharField')(default='IP', max_length=10)),
('backend', self.gf('django.db.models.fields.CharField')(default='ip', max_length=10)),
))
db.send_create_signal('libraryauth', ['Library'])
# Adding model 'Block'
db.create_table('libraryauth_block', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('library', self.gf('django.db.models.fields.related.ForeignKey')(related_name='block', to=orm['libraryauth.Library'])),
('lower', self.gf('regluit.libraryauth.models.IPAddressModelField')(unique=True, db_index=True)),
('upper', self.gf('regluit.libraryauth.models.IPAddressModelField')(db_index=True, null=True, blank=True)),
))
db.send_create_signal('libraryauth', ['Block'])
def backwards(self, orm):
# Deleting model 'Library'
db.delete_table('libraryauth_library')
# Deleting model 'Block'
db.delete_table('libraryauth_block')
models = {
'auth.group': {
@ -59,9 +71,16 @@ class Migration(SchemaMigration):
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'libraryauth.block': {
'Meta': {'ordering': "['lower']", 'object_name': 'Block'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'block'", 'to': "orm['libraryauth.Library']"}),
'lower': ('regluit.libraryauth.models.IPAddressModelField', [], {'unique': 'True', 'db_index': 'True'}),
'upper': ('regluit.libraryauth.models.IPAddressModelField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
},
'libraryauth.library': {
'Meta': {'object_name': 'Library'},
'backend': ('django.db.models.fields.CharField', [], {'default': "'IP'", 'max_length': '10'}),
'backend': ('django.db.models.fields.CharField', [], {'default': "'ip'", 'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'to': "orm['auth.User']"})
}

View File

@ -1,21 +1,20 @@
from . import authenticate
# IP address part of this of this copied from https://github.com/benliles/django-ipauth/blob/master/ipauth/models.py
from django.contrib.auth.models import User, Group
from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models
from django.db.models import Q
from django.forms import IPAddressField as BaseIPAddressField
from django.utils.translation import ugettext_lazy as _
class Library(models.Model):
'''
name and other things derive from the User
'''
user = models.OneToOneField(User, related_name='library')
backend = models.CharField(max_length=10, default='IP')
def authenticate(self, enduser):
for group in enduser.groups.all():
if enduser.username == group.name:
return true
return authenticate(enduser, self)
backend = models.CharField(max_length=10, default='ip')
@property
def group(self):
(libgroup, created)=Group.objects.get_or_create(name=self.user.username)
@ -23,5 +22,192 @@ class Library(models.Model):
def __unicode__(self):
return self.user.username
def add_user(self, user):
user.groups.add(self.group)
def has_user(self, user):
return self.group in user.groups.all()
@property
def join_template(self):
return 'libraryauth/' + self.backend + '_join.html'
def ip_to_long(value):
validators.validate_ipv4_address(value)
lower_validator = validators.MinValueValidator(0)
upper_validator = validators.MinValueValidator(255)
value = value.split('.')
output = 0
for i in range(0, 4):
validators.validate_integer(value[i])
lower_validator(value[i])
upper_validator(value[i])
output += int(value[i]) * (256**(3-i))
return output
def long_to_ip(value):
validators.validate_integer(value)
value = int(value)
validators.MinValueValidator(0)(value)
validators.MaxValueValidator(4294967295)(value)
return '%d.%d.%d.%d' % (value >> 24, value >> 16 & 255,
value >> 8 & 255, value & 255)
class IP(object):
def __init__(self, value):
self.int = value
def _set_int(self, value):
if isinstance(value, IP):
self._int = IP.int
try:
self._int = int(value)
except ValueError:
self._int = ip_to_long(value)
except (TypeError, ValidationError):
self._int = None
def _get_int(self):
return self._int
int = property(_get_int, _set_int)
def _get_str(self):
if self.int!=None:
return long_to_ip(self.int)
return ''
string = property(_get_str, _set_int)
def __eq__(self, other):
if not isinstance(other, IP):
try:
other = IP(other)
except:
return False
return self.int == other.int
def __cmp__(self, other):
if not isinstance(other, IP):
other = IP(other)
if self.int and other.int:
return self.int.__cmp__(other.int)
raise ValueError('Invalid arguments')
def __unicode__(self):
return self.string
def __str__(self):
return self.string
class IPAddressFormField(BaseIPAddressField):
default_validators = []
def prepare_value(self, value):
if isinstance(value, IP):
return value.string
try:
return IP(value).string
except:
pass
return value
def to_python(self, value):
if value==0:
return IP(0)
if value in validators.EMPTY_VALUES:
return None
try:
return IP(value)
except ValidationError:
raise ValidationError(self.default_error_messages['invalid'],
code='invalid')
class IPAddressModelField(models.IPAddressField):
__metaclass__ = models.SubfieldBase
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
models.Field.__init__(self, *args, **kwargs)
def get_internal_type(self):
return "PositiveIntegerField"
def get_prep_value(self, value):
if not value:
return value
if isinstance(value, IP):
return value.int
def to_python(self, value):
if isinstance(value, IP):
return value
try:
return IP(value)
except ValidationError:
return None
def formfield(self, **kwargs):
defaults = {'form_class': IPAddressFormField}
defaults.update(kwargs)
return super(models.IPAddressField, self).formfield(**defaults)
class Block(models.Model):
library = models.ForeignKey(Library, related_name='block')
lower = IPAddressModelField(db_index=True, unique=True)
upper = IPAddressModelField(db_index=True, blank=True, null=True)
def clean(self):
if self.upper and self.upper.int:
try:
if self.lower > self.upper:
raise ValidationError('Lower end of the Block must be less '
'than or equal to the upper end')
except ValueError, e:
pass
others = Block.objects.exclude(pk=self.pk)
query = Q(lower__lte=self.lower, upper__gte=self.lower) | \
Q(lower=self.lower)
if self.upper and self.upper.int:
textual = u'%s-%s' % (self.lower, self.upper)
query = query | Q(lower__range=(self.lower, self.upper)) | \
Q(lower__lte=self.upper, upper__gte=self.upper)
else:
textual = str(self.lower)
query = others.filter(query)
if query.exists():
values = query.distinct().values_list('library__user__username', flat=True)
raise ValidationError('%s overlaps a block in in use by: %s' % (textual,
', '.join(list(frozenset(values))[:5])))
def __unicode__(self):
if self.upper and self.upper.int:
return u'%s %s-%s' % (self.library, self.lower, self.upper)
return u'%s %s' % (self.library, self.lower)
class Meta:
ordering = ['lower',]
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^regluit\.libraryauth\.models\.IPAddressModelField"])

View File

@ -0,0 +1 @@
Denied authentication for {{ request.user }} at {{ request.META.REMOTE_ADDR }}

View File

@ -0,0 +1,5 @@
{% if authenticator.allowed %}
<a href="{% url join_library authenticator.library %}?next={% url join_library authenticator.library %}" class="fakeinput">Make this my Library</a>
{% else %}
You can't join {{ authenticator.library }} at your current location.
{% endif %}

View File

@ -0,0 +1 @@
{{params.library}}

View File

@ -0,0 +1,2 @@
{% extends "registration/from_pledge.html" %}
{% block login_pitch %}<h3>Before you can join {{library}} on unglue.it, please login or make an unglue.it account. </h3>{% endblock %}

11
libraryauth/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.conf.urls.defaults import *
from django.core.urlresolvers import reverse
from django.views.generic.simple import direct_to_template
from . import views
urlpatterns = patterns(
"",
url(r"^libraryauth/(?P<library>[^/]+)/join/$", views.join_library, name="join_library"),
url(r"^libraryauth/(?P<library>[^/]+)/deny/$", direct_to_template, {'template':'libraryauth/denied.html'}, name="bad_library"),
url(r'^accounts/superlogin/$', 'superlogin', name='superlogin'),
)

59
libraryauth/views.py Normal file
View File

@ -0,0 +1,59 @@
import logging
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from django.contrib.auth.views import login
from django.http import HttpResponseRedirect
from . import backends
from .models import Library
from .forms import AuthForm
logger = logging.getLogger(__name__)
def join_library(request, library):
library=get_object_or_404(Library, user__username=library)
return Authenticator(request,library).process(
reverse('library',args=[str(library)]),
reverse('bad_library',args=[str(library)]),
)
def superlogin(request, extra_context=None, **kwargs):
if request.method == 'POST' and request.user.is_anonymous():
username=request.POST.get("username", "")
try:
user=models.User.objects.get(username=username)
extra_context={"socials":user.profile.social_auths}
except:
pass
if request.GET.has_key("add"):
request.session["add_wishlist"]=request.GET["add"]
return login(request, extra_context=extra_context, authentication_form=AuthForm, **kwargs)
class Authenticator:
request=None
library=None
def __init__(self, request, library):
self.request=request
self.library=library
def process(self, success_url, deny_url):
logger.info('authenticator for %s at %s.'%(self.request.user, self.library))
if self.library.has_user(self.request.user):
return HttpResponseRedirect(success_url)
backend_test= getattr(backends, self.library.backend + '_authenticate')
if backend_test(self.request, self.library):
if self.request.user.is_authenticated():
self.library.add_user(self.request.user)
return HttpResponseRedirect(success_url)
else:
return superlogin(self.request, extra_context={'library':self.library}, template_name='libraryauth/library_login.html')
else:
backend_authenticator= getattr(backends, self.library.backend + '_authenticator')
return backend_authenticator(self.request, self.library, success_url, deny_url)
def allowed(self):
backend_test= getattr(backends, self.library.backend + '_authenticate')
return backend_test(self.request, self.library)

View File

@ -4,7 +4,8 @@ from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from frontend.forms import ProfileForm
from frontend.views import superlogin, social_auth_reset_password
from frontend.views import social_auth_reset_password
from libraryauth.views import superlogin
from regluit.admin import admin_site
from regluit.core.sitemaps import WorkSitemap, PublisherSitemap
@ -40,6 +41,7 @@ urlpatterns = patterns('',
(r'^api/', include('regluit.api.urls')),
(r'', include('regluit.frontend.urls')),
(r'', include('regluit.payment.urls')),
(r'', include('regluit.libraryauth.urls')),
(r'^selectable/', include('selectable.urls')),
url(r'^admin/', include(admin_site.urls)),
(r'^comments/', include('django.contrib.comments.urls')),