remove old access app and old cache and inclusion code
This commit is contained in:
parent
c1d5c0ebef
commit
904e8f199c
31 changed files with 0 additions and 1212 deletions
|
@ -1,66 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from c3nav.access.models import AccessOperator, AccessToken, AccessTokenInstance, AccessUser
|
|
||||||
|
|
||||||
|
|
||||||
class AccessOperatorInline(admin.StackedInline):
|
|
||||||
model = AccessOperator
|
|
||||||
can_delete = False
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(BaseUserAdmin):
|
|
||||||
fieldsets = (
|
|
||||||
(None, {'fields': ('username', 'password', 'email')}),
|
|
||||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
|
|
||||||
'groups', 'user_permissions')}),
|
|
||||||
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
|
|
||||||
)
|
|
||||||
readonly_fields = ('last_login', 'date_joined',)
|
|
||||||
inlines = (AccessOperatorInline, )
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.unregister(User)
|
|
||||||
admin.site.register(User, UserAdmin)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenInline(admin.TabularInline):
|
|
||||||
model = AccessToken
|
|
||||||
show_change_link = True
|
|
||||||
readonly_fields = ('author', 'permissions', 'description', 'creation_date', 'activated', 'expires', 'expired')
|
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AccessUser)
|
|
||||||
class AccessUserAdmin(admin.ModelAdmin):
|
|
||||||
inlines = (AccessTokenInline,)
|
|
||||||
list_display = ('user_url', 'creation_date', 'author', 'description')
|
|
||||||
fields = ('user_url', 'creation_date', 'author', 'description')
|
|
||||||
readonly_fields = ('creation_date', )
|
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenInstanceInline(admin.TabularInline):
|
|
||||||
model = AccessTokenInstance
|
|
||||||
fields = ('secret', 'creation_date', 'expires', )
|
|
||||||
readonly_fields = ('secret', 'creation_date', 'expires', )
|
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_change_permission(self, request, obj=None):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AccessToken)
|
|
||||||
class AccessTokenAdmin(admin.ModelAdmin):
|
|
||||||
inlines = (AccessTokenInstanceInline,)
|
|
||||||
list_display = ('__str__', 'user', 'permissions', 'author', 'creation_date', 'activated', 'expires', 'expired')
|
|
||||||
fields = ('user', 'permissions', 'author', 'creation_date', 'activated', 'expires', 'expired', 'description')
|
|
||||||
readonly_fields = ('user', 'creation_date', 'activated', 'expired', 'description')
|
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
|
||||||
return False
|
|
|
@ -1,27 +0,0 @@
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from c3nav.mapdata.inclusion import get_maybe_invisible_areas_names
|
|
||||||
|
|
||||||
|
|
||||||
def can_access(request, item):
|
|
||||||
# todo implement this
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def filter_queryset_by_access(request, queryset, filter_location_inclusion=False):
|
|
||||||
# todo implement this
|
|
||||||
return queryset if request.c3nav_full_access else queryset.filter(public=True)
|
|
||||||
|
|
||||||
|
|
||||||
def filter_arealocations_by_access(request, queryset):
|
|
||||||
# todo implement this
|
|
||||||
if request.c3nav_full_access:
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(Q(Q(public=True), ~Q(routing_inclusion='needs_permission')) |
|
|
||||||
Q(name__in=request.c3nav_access_list))
|
|
||||||
|
|
||||||
|
|
||||||
def get_visible_areas(request):
|
|
||||||
areas = [':full' if request.c3nav_full_access else ':base']
|
|
||||||
areas += [name for name in get_maybe_invisible_areas_names() if name in request.c3nav_access_list]
|
|
||||||
return areas
|
|
|
@ -1,76 +0,0 @@
|
||||||
from django.forms import ModelForm, MultipleChoiceField
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from c3nav.access.models import AccessToken, AccessUser
|
|
||||||
|
|
||||||
|
|
||||||
def get_permissions_field(request):
|
|
||||||
locations = AreaLocation.objects.filter(routing_inclusion='needs_permission').order_by('name')
|
|
||||||
|
|
||||||
has_operator = True
|
|
||||||
try:
|
|
||||||
request.user.operator
|
|
||||||
except:
|
|
||||||
has_operator = False
|
|
||||||
|
|
||||||
OPTIONS = []
|
|
||||||
can_full = False
|
|
||||||
if request.user.is_superuser:
|
|
||||||
can_full = True
|
|
||||||
elif has_operator:
|
|
||||||
can_award = request.user.operator.can_award_permissions.split(';')
|
|
||||||
can_full = ':full' in can_award
|
|
||||||
if not can_full:
|
|
||||||
locations = locations.filter(name__in=can_award)
|
|
||||||
else:
|
|
||||||
locations = []
|
|
||||||
|
|
||||||
if can_full:
|
|
||||||
OPTIONS.append((':full', _('Full Permissions')))
|
|
||||||
|
|
||||||
locationgroups = {}
|
|
||||||
locationgroups_count = {}
|
|
||||||
for location in locations:
|
|
||||||
for group in location.groups.all():
|
|
||||||
if group.location_id not in locationgroups:
|
|
||||||
locationgroups[group.location_id] = group
|
|
||||||
locationgroups_count[group.location_id] = 0
|
|
||||||
locationgroups_count[group.location_id] += 1
|
|
||||||
|
|
||||||
locationgroup_options = []
|
|
||||||
for location_id, group in locationgroups.items():
|
|
||||||
locationgroup_options.append((location_id, _('%(grouptitle)s (%(count)s nonpublic locations)') % {
|
|
||||||
'grouptitle': group.title,
|
|
||||||
'count': locationgroups_count[location_id]
|
|
||||||
}))
|
|
||||||
|
|
||||||
OPTIONS += sorted(locationgroup_options)
|
|
||||||
|
|
||||||
if can_full:
|
|
||||||
OPTIONS.append((':full', _('Full Permissions')))
|
|
||||||
|
|
||||||
OPTIONS += [(location.name, location.title) for location in locations]
|
|
||||||
return MultipleChoiceField(choices=OPTIONS, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenForm(ModelForm):
|
|
||||||
def __init__(self, *args, request, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['permissions'] = get_permissions_field(request)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AccessToken
|
|
||||||
fields = ['permissions', 'description', 'expires']
|
|
||||||
|
|
||||||
def clean_permissions(self):
|
|
||||||
data = self.cleaned_data['permissions']
|
|
||||||
if ':full' in data:
|
|
||||||
data = [':full']
|
|
||||||
data = ';'.join(data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class AccessUserForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = AccessUser
|
|
||||||
fields = ['user_url', 'description']
|
|
|
@ -1,57 +0,0 @@
|
||||||
import re
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from c3nav.access.models import AccessTokenInstance
|
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenMiddleware:
|
|
||||||
def __init__(self, get_response):
|
|
||||||
self.get_response = get_response
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
request.c3nav_access_instance = None
|
|
||||||
request.c3nav_access = None
|
|
||||||
request.c3nav_new_access = False
|
|
||||||
|
|
||||||
request.c3nav_full_access = settings.DEBUG
|
|
||||||
request.c3nav_access_list = ()
|
|
||||||
|
|
||||||
access_cookie = request.COOKIES.get('c3nav_access')
|
|
||||||
if access_cookie and re.match(r'^[0-9]+:[a-zA-Z0-9]+$', access_cookie):
|
|
||||||
pk, secret = access_cookie.split(':')
|
|
||||||
queryset = AccessTokenInstance.objects.filter(Q(access_token__id=int(pk), secret=secret),
|
|
||||||
Q(expires__isnull=True) | Q(expires__gt=timezone.now()),
|
|
||||||
Q(access_token__expired=False),
|
|
||||||
Q(access_token__expires__isnull=True) |
|
|
||||||
Q(access_token__expires__gt=timezone.now()))
|
|
||||||
access_instance = queryset.select_related('access_token').first()
|
|
||||||
if access_instance:
|
|
||||||
request.c3nav_access_instance = access_instance
|
|
||||||
request.c3nav_access = access_instance.access_token
|
|
||||||
request.c3nav_access.instances.filter(creation_date__lt=access_instance.creation_date).delete()
|
|
||||||
|
|
||||||
request.c3nav_full_access = request.c3nav_access.full_access
|
|
||||||
request.c3nav_access_list = request.c3nav_access.permissions_list
|
|
||||||
|
|
||||||
response = self.get_response(request)
|
|
||||||
|
|
||||||
if request.c3nav_access is not None:
|
|
||||||
with transaction.atomic():
|
|
||||||
cookie_value = request.c3nav_access.new_instance()
|
|
||||||
response.set_cookie('c3nav_access', cookie_value, expires=timezone.now() + timedelta(days=30))
|
|
||||||
|
|
||||||
if request.c3nav_new_access:
|
|
||||||
request.c3nav_access.activated = True
|
|
||||||
request.c3nav_access.save()
|
|
||||||
|
|
||||||
if request.c3nav_access_instance:
|
|
||||||
access_token = request.c3nav_access_instance.access_token
|
|
||||||
access_token.expired = True
|
|
||||||
access_token.save()
|
|
||||||
|
|
||||||
return response
|
|
|
@ -1,83 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.4 on 2016-12-21 17:21
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AccessOperator',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='description')),
|
|
||||||
('can_award_permissions', models.CharField(max_length=2048, verbose_name='can award permissions')),
|
|
||||||
('access_from', models.DateTimeField(blank=True, null=True, verbose_name='has access from')),
|
|
||||||
('access_until', models.DateTimeField(blank=True, null=True, verbose_name='has access until')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='operator', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name_plural': 'Access Operator',
|
|
||||||
'verbose_name': 'Access Operator',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AccessToken',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('permissions', models.CharField(max_length=2048, verbose_name='permissions')),
|
|
||||||
('description', models.CharField(max_length=200, verbose_name='description')),
|
|
||||||
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
|
|
||||||
('expires', models.DateTimeField(blank=True, null=True)),
|
|
||||||
('expired', models.BooleanField(default=False, verbose_name='is expired')),
|
|
||||||
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='creator')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name_plural': 'Access Tokens',
|
|
||||||
'verbose_name': 'Access Token',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AccessTokenInstance',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('secret', models.CharField(max_length=42, verbose_name='access secret')),
|
|
||||||
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
|
|
||||||
('expires', models.DateTimeField(null=True)),
|
|
||||||
('access_token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='access.AccessToken', verbose_name='Access Token')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name_plural': 'Access Tokens Instance',
|
|
||||||
'verbose_name': 'Access Token Instance',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AccessUser',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('user_url', models.CharField(help_text='Usually an URL to a profile somewhere', max_length=200, unique=True, verbose_name='access name')),
|
|
||||||
('description', models.TextField(blank=True, max_length=200, null=True, verbose_name='description')),
|
|
||||||
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
|
|
||||||
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='access.AccessOperator', verbose_name='creator')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name_plural': 'Access Users',
|
|
||||||
'verbose_name': 'Access User',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='accesstoken',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='access.AccessUser', verbose_name='Access User'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.4 on 2016-12-21 17:39
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('access', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='accesstoken',
|
|
||||||
name='activated',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='activated'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='accesstoken',
|
|
||||||
name='secret',
|
|
||||||
field=models.CharField(default='', max_length=42, verbose_name='activation secret'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,27 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.4 on 2016-12-21 23:11
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('access', '0002_auto_20161221_1739'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='accessoperator',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, default='', verbose_name='description'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='accessuser',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, default='', max_length=200, verbose_name='description'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.4 on 2016-12-23 22:25
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('access', '0003_auto_20161221_2311'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='accesstokeninstance',
|
|
||||||
options={'verbose_name': 'Access Token Instance', 'verbose_name_plural': 'Access Token Instances'},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,22 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.4 on 2016-12-25 10:18
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('access', '0004_auto_20161223_2225'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='accessuser',
|
|
||||||
name='author',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='creator'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,126 +0,0 @@
|
||||||
import string
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models, transaction
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
from django.utils.functional import cached_property
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class AccessOperator(models.Model):
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='operator')
|
|
||||||
description = models.TextField(_('description'), blank=True)
|
|
||||||
can_award_permissions = models.CharField(_('can award permissions'), max_length=2048)
|
|
||||||
access_from = models.DateTimeField(_('has access from'), null=True, blank=True)
|
|
||||||
access_until = models.DateTimeField(_('has access until'), null=True, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Access Operator')
|
|
||||||
verbose_name_plural = _('Access Operator')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessUser(models.Model):
|
|
||||||
user_url = models.CharField(_('access name'), unique=True, max_length=200,
|
|
||||||
help_text=_('Usually an URL to a profile somewhere'))
|
|
||||||
author = models.ForeignKey(User, on_delete=models.PROTECT, null=True, blank=True,
|
|
||||||
verbose_name=_('creator'))
|
|
||||||
description = models.TextField(_('description'), max_length=200, blank=True)
|
|
||||||
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Access User')
|
|
||||||
verbose_name_plural = _('Access Users')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def valid_tokens(self):
|
|
||||||
return self.tokens.filter(Q(expired=False) | Q(expires__isnull=False, expires__lt=timezone.now()))
|
|
||||||
|
|
||||||
def new_token(self, **kwargs):
|
|
||||||
kwargs['secret'] = AccessToken.create_secret()
|
|
||||||
return self.tokens.create(**kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.user_url
|
|
||||||
|
|
||||||
|
|
||||||
class AccessToken(models.Model):
|
|
||||||
user = models.ForeignKey(AccessUser, on_delete=models.CASCADE, related_name='tokens',
|
|
||||||
verbose_name=_('Access User'))
|
|
||||||
author = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=_('creator'), null=True, blank=True)
|
|
||||||
permissions = models.CharField(_('permissions'), max_length=2048)
|
|
||||||
description = models.CharField(_('description'), max_length=200)
|
|
||||||
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
|
|
||||||
expires = models.DateTimeField(null=True, blank=True)
|
|
||||||
expired = models.BooleanField(_('is expired'), default=False)
|
|
||||||
activated = models.BooleanField(_('activated'), default=False)
|
|
||||||
secret = models.CharField(_('activation secret'), max_length=42)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Access Token')
|
|
||||||
verbose_name_plural = _('Access Tokens')
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def permissions_list(self):
|
|
||||||
return self.permissions.split(';')
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def permissions_list_objects(self):
|
|
||||||
return AreaLocation.objects.filter(name__in=self.permissions_list)
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def full_access(self):
|
|
||||||
return ':full' in self.permissions_list
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_expired(self):
|
|
||||||
return self.expired or (self.expires is not None and self.expires < timezone.now())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def activation_url(self):
|
|
||||||
if self.activated:
|
|
||||||
return None
|
|
||||||
return reverse('access.activate', kwargs={'pk': self.pk, 'secret': self.secret})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def qr_url(self):
|
|
||||||
if self.activated:
|
|
||||||
return None
|
|
||||||
return reverse('access.qr', kwargs={'pk': self.pk, 'secret': self.secret})
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_secret():
|
|
||||||
return get_random_string(42, string.ascii_letters + string.digits)
|
|
||||||
|
|
||||||
def new_instance(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
for instance in self.instances.filter(expires__isnull=True):
|
|
||||||
instance.expires = timezone.now()+timedelta(seconds=5)
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
self.instances.filter(expires__isnull=False, expires__lt=timezone.now()).delete()
|
|
||||||
|
|
||||||
secret = self.create_secret()
|
|
||||||
self.instances.create(secret=secret)
|
|
||||||
return '%d:%s' % (self.pk, secret)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '%s #%d' % (_('Access Token'), self.id)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenInstance(models.Model):
|
|
||||||
access_token = models.ForeignKey(AccessToken, on_delete=models.CASCADE, related_name='instances',
|
|
||||||
verbose_name=_('Access Token'))
|
|
||||||
secret = models.CharField(_('access secret'), max_length=42)
|
|
||||||
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
|
|
||||||
expires = models.DateTimeField(null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Access Token Instance')
|
|
||||||
verbose_name_plural = _('Access Token Instances')
|
|
|
@ -1,44 +0,0 @@
|
||||||
body, .btn {
|
|
||||||
font-size:16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom:20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .container {
|
|
||||||
max-width:420px;
|
|
||||||
}
|
|
||||||
.pager .middle {
|
|
||||||
top:3px;
|
|
||||||
position:relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
footer {
|
|
||||||
text-align:center;
|
|
||||||
}
|
|
||||||
.languages {
|
|
||||||
margin-bottom:0;
|
|
||||||
height:2px;
|
|
||||||
}
|
|
||||||
.languages button {
|
|
||||||
text-transform:none;
|
|
||||||
font-weight:normal;
|
|
||||||
margin-top:0 !important;
|
|
||||||
border-width:0 !important;
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.table .btn {
|
|
||||||
font-size:14px;
|
|
||||||
}
|
|
||||||
.table tbody tr td {
|
|
||||||
min-height: 29px;
|
|
||||||
line-height:29px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token img {
|
|
||||||
width:100%;
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends 'access/base.html' %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bodyclass %}login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if success %}
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<strong>{% trans 'Your access token was installed on your device!' %}</strong>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{% include 'access/fragment_token.html' %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
{% load compress %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>c3nav control panel</title>
|
|
||||||
{% compress css %}
|
|
||||||
<link href="{% static 'bootstrap/css/bootstrap.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'access/css/c3nav-access.css' %}" rel="stylesheet">
|
|
||||||
{% endcompress %}
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="{% block bodyclass %}{% endblock %}">
|
|
||||||
|
|
||||||
<div class="container" id="main">
|
|
||||||
<h1>c3nav access control</h1>
|
|
||||||
{% block nav %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% include 'site/footer.html' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% compress js %}
|
|
||||||
<script type="text/javascript" src="{% static 'jquery/jquery.js' %}"></script>
|
|
||||||
{% endcompress %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,17 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<form action="{{ token.activation_url }}" method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="token">
|
|
||||||
<p>
|
|
||||||
<img src="{{ token.qr_url }}">
|
|
||||||
</p>
|
|
||||||
<p><a href="{{ token.activation_url }}">{% trans 'Link to this token / in this QR code' %}</a></p>
|
|
||||||
<p>
|
|
||||||
<button type="submit" class="btn btn-default btn-block">{% trans 'Activate token on this device' %}</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{% blocktrans %}This link QR Code / Link only works once. If you want access on multiple devices, you need multiple tokens.{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
|
@ -1,10 +0,0 @@
|
||||||
{% extends 'access/base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block nav %}
|
|
||||||
<ul class="nav nav-pills">
|
|
||||||
<li class="active"><a href="{% url 'access.users' %}">{% trans 'Users' %}</a></li>
|
|
||||||
<li><a href="{% url 'access.logout' %}">{% trans 'Log out' %}</a></li>
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
|
@ -1,24 +0,0 @@
|
||||||
{% extends 'access/base.html' %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bodyclass %}login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
|
||||||
<legend>{% trans 'Log in' %}</legend>
|
|
||||||
{% bootstrap_form form %}
|
|
||||||
<div class="form-group">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans 'Log in' %}</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<p>{% blocktrans %}This is the non-public backend for creation of access tokens.{% endblocktrans %}</p>
|
|
||||||
|
|
||||||
<p><a class="btn btn-sm btn-default btn-block" href="{% url 'access.prove' %}">
|
|
||||||
{% blocktrans %}Prove that you should have access{% endblocktrans %}
|
|
||||||
</a></p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,83 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% extends 'access/loggedin_base.html' %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h3>Access Tokens <small>{{ user.user_url }}</small></h3>
|
|
||||||
<p>{{ user.description }}</p>
|
|
||||||
<form method="POST">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<h4>{% trans 'Add new access token' %}</h4>
|
|
||||||
<div class="row">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% bootstrap_form new_token_form form_group_class='form-group col-md-3' %}
|
|
||||||
<div class="form-group col-md-3">
|
|
||||||
<label class="control-label"> </label>
|
|
||||||
<button type="submit" name="create" class="btn btn-primary btn-block btn-sm">{% trans 'Add' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
<form method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans 'ID' %}</th>
|
|
||||||
<th>{% trans 'Description' %}</th>
|
|
||||||
<th>{% trans 'Author' %}</th>
|
|
||||||
<th>{% trans 'Permissions' %}</th>
|
|
||||||
<th>{% trans 'Creation Date' %}</th>
|
|
||||||
<th>{% trans 'Expiration' %}</th>
|
|
||||||
<th>{% trans 'Action' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for token in tokens %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ token.id }}</td>
|
|
||||||
<td>{{ token.description }}</td>
|
|
||||||
<td>{% if token.author %}{{ token.author }}{% endif %}</td>
|
|
||||||
<td>
|
|
||||||
{% if token.full_access %}
|
|
||||||
<span class="text-success">all</span>
|
|
||||||
{% else %}
|
|
||||||
{% for location in token.permissions_list_objects %}
|
|
||||||
{{ location.title }},
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ token.creation_date|date:"SHORT_DATETIME_FORMAT" }}</td>
|
|
||||||
<td>
|
|
||||||
{% if token.is_expired %}
|
|
||||||
{% trans 'expired' %}
|
|
||||||
{% elif token.expires %}
|
|
||||||
{{ token.expires|date:"SHORT_DATETIME_FORMAT" }}
|
|
||||||
{% else %}
|
|
||||||
{% trans 'never' %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if not token.is_expired %}
|
|
||||||
<button class="btn btn-xs btn-danger" type="submit" name="expire" value="{{ token.id }}">{% trans 'Expire' %}</button>
|
|
||||||
{% if not token.activated %}
|
|
||||||
<a href="{% url 'access.user.token' user=user.id token=token.id %}" class="btn btn-xs btn-success" type="submit" name="activate" value="{{ token.id }}">{% trans 'Activate' %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends 'access/loggedin_base.html' %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 col-md-offset-4">
|
|
||||||
{% include 'access/fragment_token.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p><a href="{% url 'access.user' pk=user.id %}">{% trans 'back to user' %}</a></p>
|
|
||||||
{% endblock %}
|
|
|
@ -1,70 +0,0 @@
|
||||||
{% extends 'access/loggedin_base.html' %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Users</h2>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans 'ID' %}</th>
|
|
||||||
<th>{% trans 'Name' %}</th>
|
|
||||||
<th>{% trans 'Author' %}</th>
|
|
||||||
<th>{% trans 'Description' %}</th>
|
|
||||||
<th>{% trans 'Active Tokens' %}</th>
|
|
||||||
<th>{% trans 'Creation Date' %}</th>
|
|
||||||
<th>{% trans 'Details' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for user in users %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ user.id }}</td>
|
|
||||||
<td>{{ user.user_url }}</td>
|
|
||||||
<td>{% if user.author %}{{ user.author }}{% endif %}</td>
|
|
||||||
<td>{{ user.description }}</td>
|
|
||||||
<td>{{ user.valid_tokens.count }}</td>
|
|
||||||
<td>{{ user.creation_date|date:"SHORT_DATETIME_FORMAT" }}</td>
|
|
||||||
<td><a href="{% url 'access.user' pk=user.pk %}">{% trans 'Details' %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ul class="pager">
|
|
||||||
{% if users.has_previous %}
|
|
||||||
<li class="previous"><a href="{% url 'acces.users' page=users.previous_page_number %}">« {% trans 'previous' %}</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li class="previous disabled"><a href="#">« {% trans 'previous' %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="middle">
|
|
||||||
{% blocktrans with number=users.number total=users.paginator.num_pages %}Page {{ number }} of {{ total }}{% endblocktrans %}
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if users.has_next %}
|
|
||||||
<li class="next"><a href="{% url 'acces.users' page=users.next_page_number %}">{% trans 'next' %} »</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li class="next disabled"><a href="#">{% trans 'next' %} »</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<h4>{% trans 'Add new access user' %}</h4>
|
|
||||||
<div class="row">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% bootstrap_form new_user_form form_group_class='form-group col-md-4' %}
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label class="control-label"> </label>
|
|
||||||
<button type="submit" name="create" class="btn btn-primary btn-block btn-sm">{% trans 'Add new User' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,16 +0,0 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.auth import views as auth_views
|
|
||||||
|
|
||||||
from c3nav.access.views import activate_token, dashboard, show_user_token, token_qr, user_detail, user_list
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', dashboard, name='access.dashboard'),
|
|
||||||
url(r'^activate/(?P<pk>[0-9]+):(?P<secret>[a-zA-Z0-9]+)/$', activate_token, name='access.activate'),
|
|
||||||
url(r'^qr/(?P<pk>[0-9]+):(?P<secret>[a-zA-Z0-9]+).png$', token_qr, name='access.qr'),
|
|
||||||
url(r'^users/$', user_list, name='access.users'),
|
|
||||||
url(r'^users/(?P<page>[0-9]+)/$', user_list, name='access.users'),
|
|
||||||
url(r'^user/(?P<pk>[0-9]+)/$', user_detail, name='access.user'),
|
|
||||||
url(r'^user/(?P<user>[0-9]+)/(?P<token>[0-9]+)/$', show_user_token, name='access.user.token'),
|
|
||||||
url(r'^login/$', auth_views.login, {'template_name': 'access/login.html'}, name='access.login'),
|
|
||||||
url(r'^logout/$', auth_views.logout, name='access.logout'),
|
|
||||||
]
|
|
|
@ -1,118 +0,0 @@
|
||||||
import qrcode
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from c3nav.access.forms import AccessTokenForm, AccessUserForm
|
|
||||||
from c3nav.access.models import AccessToken, AccessUser
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url='/access/login/')
|
|
||||||
def dashboard(request):
|
|
||||||
return redirect('access.users')
|
|
||||||
|
|
||||||
|
|
||||||
def activate_token(request, pk, secret):
|
|
||||||
token = get_object_or_404(AccessToken, expired=False, activated=False, id=pk, secret=secret)
|
|
||||||
if request.method == 'POST':
|
|
||||||
request.c3nav_access = token
|
|
||||||
request.c3nav_new_access = True
|
|
||||||
return render(request, 'access/activate.html', context={
|
|
||||||
'success': True,
|
|
||||||
})
|
|
||||||
|
|
||||||
return render(request, 'access/activate.html', context={
|
|
||||||
'token': token,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def token_qr(request, pk, secret):
|
|
||||||
get_object_or_404(AccessToken, expired=False, activated=False, id=pk, secret=secret)
|
|
||||||
|
|
||||||
qr = qrcode.QRCode(
|
|
||||||
version=1,
|
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
|
||||||
box_size=10,
|
|
||||||
border=4,
|
|
||||||
)
|
|
||||||
qr.add_data(request.build_absolute_uri(reverse('access.activate', kwargs={'pk': pk, 'secret': secret})))
|
|
||||||
qr.make(fit=True)
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='image/png')
|
|
||||||
qr.make_image().save(response, 'PNG')
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url='/access/login/')
|
|
||||||
def user_list(request, page=1):
|
|
||||||
queryset = AccessUser.objects.all()
|
|
||||||
paginator = Paginator(queryset, 25)
|
|
||||||
|
|
||||||
try:
|
|
||||||
users = paginator.page(page)
|
|
||||||
except PageNotAnInteger:
|
|
||||||
return redirect('access.users')
|
|
||||||
except EmptyPage:
|
|
||||||
return redirect('access.users')
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
new_user_form = AccessUserForm(data=request.POST)
|
|
||||||
if new_user_form.is_valid():
|
|
||||||
user = new_user_form.instance
|
|
||||||
user.author = request.user
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
return redirect('access.user', pk=user.id)
|
|
||||||
else:
|
|
||||||
new_user_form = AccessUserForm()
|
|
||||||
|
|
||||||
return render(request, 'access/users.html', {
|
|
||||||
'users': users,
|
|
||||||
'new_user_form': new_user_form,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url='/access/login/')
|
|
||||||
def user_detail(request, pk):
|
|
||||||
user = get_object_or_404(AccessUser, id=pk)
|
|
||||||
|
|
||||||
tokens = user.tokens.order_by('-creation_date')
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'expire' in request.POST:
|
|
||||||
token = get_object_or_404(AccessToken, user=user, id=request.POST['expire'])
|
|
||||||
token.expired = True
|
|
||||||
token.save()
|
|
||||||
return redirect('access.user', pk=user.id)
|
|
||||||
|
|
||||||
new_token_form = AccessTokenForm(data=request.POST, request=request)
|
|
||||||
if new_token_form.is_valid():
|
|
||||||
token = new_token_form.instance
|
|
||||||
token.user = user
|
|
||||||
token.secret = AccessToken.create_secret()
|
|
||||||
|
|
||||||
token.author = request.user
|
|
||||||
token.save()
|
|
||||||
|
|
||||||
return redirect('access.user.token', user=user.id, token=token.id)
|
|
||||||
else:
|
|
||||||
new_token_form = AccessTokenForm(request=request)
|
|
||||||
|
|
||||||
return render(request, 'access/user.html', {
|
|
||||||
'user': user,
|
|
||||||
'new_token_form': new_token_form,
|
|
||||||
'tokens': tokens,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url='/access/login/')
|
|
||||||
def show_user_token(request, user, token):
|
|
||||||
user = get_object_or_404(AccessUser, id=user)
|
|
||||||
token = get_object_or_404(AccessToken, user=user, id=token)
|
|
||||||
|
|
||||||
return render(request, 'access/user_token.html', {
|
|
||||||
'user': user,
|
|
||||||
'token': token,
|
|
||||||
})
|
|
|
@ -3,7 +3,6 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
from c3nav.access.apply import can_access, filter_queryset_by_access
|
|
||||||
from c3nav.mapdata.models import Section
|
from c3nav.mapdata.models import Section
|
||||||
from c3nav.mapdata.models.base import EDITOR_FORM_MODELS
|
from c3nav.mapdata.models.base import EDITOR_FORM_MODELS
|
||||||
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_include_avoid():
|
|
||||||
include = set()
|
|
||||||
avoid = set()
|
|
||||||
|
|
||||||
locations = list(AreaLocation.objects.exclude(routing_inclusion='default'))
|
|
||||||
|
|
||||||
for location in locations:
|
|
||||||
if location.routing_inclusion != 'allow_avoid':
|
|
||||||
avoid.add(location.location_id)
|
|
||||||
|
|
||||||
return include, avoid
|
|
||||||
|
|
||||||
|
|
||||||
# Todo: add cache
|
|
||||||
def get_includables_avoidables(request):
|
|
||||||
includables = []
|
|
||||||
avoidables = []
|
|
||||||
|
|
||||||
locations = list(AreaLocation.objects.exclude(routing_inclusion='default'))
|
|
||||||
|
|
||||||
if request.c3nav_full_access:
|
|
||||||
includables.append((':nonpublic', _('non-public areas')))
|
|
||||||
avoidables.append((':public', _('public areas')))
|
|
||||||
|
|
||||||
for location in locations:
|
|
||||||
item = (location.location_id, location.title)
|
|
||||||
|
|
||||||
if location.location_id not in request.c3nav_access_list and not request.c3nav_full_access:
|
|
||||||
if location.routing_inclusion == 'needs_permission':
|
|
||||||
continue
|
|
||||||
|
|
||||||
if location.routing_inclusion == 'allow_avoid':
|
|
||||||
avoidables.append(item)
|
|
||||||
else:
|
|
||||||
includables.append(item)
|
|
||||||
|
|
||||||
return OrderedDict(includables), OrderedDict(avoidables)
|
|
||||||
|
|
||||||
|
|
||||||
def get_maybe_invisible_areas():
|
|
||||||
return AreaLocation.objects.exclude(routing_inclusion='default')
|
|
||||||
|
|
||||||
|
|
||||||
def get_maybe_invisible_areas_names():
|
|
||||||
return tuple(area.name for area in get_maybe_invisible_areas())
|
|
||||||
|
|
||||||
|
|
||||||
def parse_include_avoid(request, include_input, avoid_input):
|
|
||||||
includable, avoidable = get_includables_avoidables(request)
|
|
||||||
include_input = set(include_input) & set(includable)
|
|
||||||
avoid_input = set(avoid_input) & set(avoidable)
|
|
||||||
|
|
||||||
default_include, default_avoid = get_default_include_avoid()
|
|
||||||
|
|
||||||
include = set(default_include) | include_input
|
|
||||||
avoid = set(default_avoid) - include_input | avoid_input
|
|
||||||
|
|
||||||
return ':nonpublic' in includable, include, avoid
|
|
|
@ -1,33 +0,0 @@
|
||||||
import os
|
|
||||||
import pickle
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
last_mapdata_update_filename = os.path.join(settings.DATA_DIR, 'last_mapdata_update')
|
|
||||||
last_mapdata_update_decorator_depth = 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_mapdata_update(default_now=False):
|
|
||||||
try:
|
|
||||||
with open(last_mapdata_update_filename, 'rb') as f:
|
|
||||||
return pickle.load(f)
|
|
||||||
except:
|
|
||||||
return timezone.now() if default_now else None
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def set_last_mapdata_update():
|
|
||||||
global last_mapdata_update_decorator_depth
|
|
||||||
if last_mapdata_update_decorator_depth == 0:
|
|
||||||
try:
|
|
||||||
os.remove(last_mapdata_update_filename)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
last_mapdata_update_decorator_depth += 1
|
|
||||||
yield
|
|
||||||
last_mapdata_update_decorator_depth -= 1
|
|
||||||
if last_mapdata_update_decorator_depth == 0:
|
|
||||||
with open(last_mapdata_update_filename, 'wb') as f:
|
|
||||||
pickle.dump(timezone.now(), f)
|
|
|
@ -1,97 +0,0 @@
|
||||||
from calendar import timegm
|
|
||||||
from collections import OrderedDict
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.http import http_date
|
|
||||||
from rest_framework.response import Response as APIResponse
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
|
|
||||||
from c3nav.mapdata.lastupdate import get_last_mapdata_update
|
|
||||||
|
|
||||||
|
|
||||||
def cache_result(cache_key, timeout=900):
|
|
||||||
def decorator(func):
|
|
||||||
@wraps(func)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
last_update = get_last_mapdata_update()
|
|
||||||
if last_update is None:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
result = cache.get(cache_key)
|
|
||||||
if not result:
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
cache.set(cache_key, result, timeout)
|
|
||||||
return result
|
|
||||||
return inner
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def cache_mapdata_api_response(timeout=900):
|
|
||||||
def decorator(func):
|
|
||||||
@wraps(func)
|
|
||||||
def inner(self, request, *args, add_cache_key=None, **kwargs):
|
|
||||||
last_update = get_last_mapdata_update()
|
|
||||||
if last_update is None:
|
|
||||||
return func(self, request, *args, **kwargs)
|
|
||||||
|
|
||||||
cache_key = '__'.join((
|
|
||||||
'c3nav__mapdata__api',
|
|
||||||
last_update.isoformat(),
|
|
||||||
add_cache_key if add_cache_key is not None else '',
|
|
||||||
request.accepted_renderer.format if isinstance(self, APIView) else '',
|
|
||||||
request.path,
|
|
||||||
))
|
|
||||||
|
|
||||||
response = cache.get(cache_key)
|
|
||||||
if not response:
|
|
||||||
response = func(self, request, *args, **kwargs)
|
|
||||||
response['Last-Modifed'] = http_date(timegm(last_update.utctimetuple()))
|
|
||||||
if isinstance(response, APIResponse):
|
|
||||||
response = self.finalize_response(request, response, *args, **kwargs)
|
|
||||||
response.render()
|
|
||||||
if response.status_code < 400:
|
|
||||||
cache.set(cache_key, response, timeout)
|
|
||||||
|
|
||||||
return response
|
|
||||||
return inner
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
class CachedReadOnlyViewSetMixin():
|
|
||||||
def _get_add_cache_key(self, request, add_cache_key=''):
|
|
||||||
cache_key = add_cache_key
|
|
||||||
return cache_key
|
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
|
||||||
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
|
|
||||||
return self._list(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@cache_mapdata_api_response()
|
|
||||||
def _list(self, request, *args, **kwargs):
|
|
||||||
return super().list(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
|
|
||||||
return self._retrieve(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@cache_mapdata_api_response()
|
|
||||||
def _retrieve(self, request, *args, **kwargs):
|
|
||||||
return super().retrieve(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@cache_result('c3nav__mapdata__sections')
|
|
||||||
def get_sections_cached():
|
|
||||||
from c3nav.mapdata.models.section import Section
|
|
||||||
return OrderedDict((section.id, section) for section in Section.objects.all())
|
|
||||||
|
|
||||||
|
|
||||||
@cache_result('c3nav__mapdata__bssids')
|
|
||||||
def get_bssid_areas_cached():
|
|
||||||
from c3nav.mapdata.models import AreaLocation
|
|
||||||
bssids = {}
|
|
||||||
for area in AreaLocation.objects.filter(~Q(bssids='')):
|
|
||||||
for bssid in area.bssids.split('\n'):
|
|
||||||
bssids[bssid.strip()] = area.name
|
|
||||||
return bssids
|
|
|
@ -1,37 +1,3 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from shapely.geometry import box
|
|
||||||
from shapely.ops import cascaded_union
|
|
||||||
|
|
||||||
from c3nav.mapdata.utils.cache import cache_result
|
|
||||||
|
|
||||||
|
|
||||||
@cache_result('c3nav__mapdata__dimensions')
|
|
||||||
def get_dimensions():
|
def get_dimensions():
|
||||||
# todo calculate this
|
# todo calculate this
|
||||||
return (400, 240)
|
return (400, 240)
|
||||||
|
|
||||||
|
|
||||||
@cache_result('c3nav__mapdata__render_dimensions')
|
|
||||||
def get_render_dimensions():
|
|
||||||
width, height = get_dimensions()
|
|
||||||
return (width * settings.RENDER_SCALE, height * settings.RENDER_SCALE)
|
|
||||||
|
|
||||||
|
|
||||||
def get_render_path(filetype, level, mode, public):
|
|
||||||
return os.path.join(settings.RENDER_ROOT,
|
|
||||||
'%s%s-level-%s.%s' % (('public-' if public else ''), mode, level, filetype))
|
|
||||||
|
|
||||||
|
|
||||||
def get_public_private_area(level):
|
|
||||||
from c3nav.mapdata.models import AreaLocation
|
|
||||||
|
|
||||||
width, height = get_dimensions()
|
|
||||||
everything = box(0, 0, width, height)
|
|
||||||
needs_permission = [location.geometry
|
|
||||||
for location in AreaLocation.objects.filter(level=level,
|
|
||||||
routing_inclusion='needs_permission')]
|
|
||||||
public_area = level.public_geometries.areas_and_doors.difference(cascaded_union(needs_permission))
|
|
||||||
private_area = everything.difference(public_area)
|
|
||||||
return public_area, private_area
|
|
||||||
|
|
|
@ -138,7 +138,6 @@ INSTALLED_APPS = [
|
||||||
'c3nav.api',
|
'c3nav.api',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'c3nav.mapdata',
|
'c3nav.mapdata',
|
||||||
'c3nav.access',
|
|
||||||
'c3nav.routing',
|
'c3nav.routing',
|
||||||
'c3nav.site',
|
'c3nav.site',
|
||||||
'c3nav.editor',
|
'c3nav.editor',
|
||||||
|
@ -154,7 +153,6 @@ MIDDLEWARE = [
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'c3nav.access.middleware.AccessTokenMiddleware',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -7,14 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from c3nav.access.apply import get_visible_areas
|
|
||||||
from c3nav.mapdata.inclusion import get_includables_avoidables, parse_include_avoid
|
|
||||||
from c3nav.mapdata.lastupdate import get_last_mapdata_update
|
|
||||||
from c3nav.mapdata.models.section import Section
|
from c3nav.mapdata.models.section import Section
|
||||||
from c3nav.mapdata.utils.cache import get_sections_cached
|
|
||||||
from c3nav.mapdata.utils.misc import get_dimensions, get_render_path
|
|
||||||
from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable
|
|
||||||
from c3nav.routing.graph import Graph
|
|
||||||
|
|
||||||
ctype_mapping = {
|
ctype_mapping = {
|
||||||
'yes': ('up', 'down'),
|
'yes': ('up', 'down'),
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
import c3nav.access.urls
|
|
||||||
import c3nav.api.urls
|
import c3nav.api.urls
|
||||||
import c3nav.editor.urls
|
import c3nav.editor.urls
|
||||||
import c3nav.site.urls
|
import c3nav.site.urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^access/', include(c3nav.access.urls)),
|
|
||||||
url(r'^editor/', include(c3nav.editor.urls)),
|
url(r'^editor/', include(c3nav.editor.urls)),
|
||||||
url(r'^api/', include(c3nav.api.urls, namespace='api')),
|
url(r'^api/', include(c3nav.api.urls, namespace='api')),
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue