Interface cleanup
This commit is contained in:
parent
4164d56dea
commit
5e0e165037
@ -2,28 +2,33 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class EnsembleAdmin(admin.ModelAdmin):
|
class EnsembleAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name', 'slug']
|
list_display = ["name", "slug"]
|
||||||
|
|
||||||
|
|
||||||
class ModuleInline(admin.StackedInline):
|
class ModuleInline(admin.StackedInline):
|
||||||
model = models.Module
|
model = models.Module
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
class ProjectAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
list_display = ['name', 'ensemble', 'event_date', 'active']
|
class ProjectAdmin(admin.ModelAdmin):
|
||||||
list_filter = ['ensemble', 'active']
|
list_display = ["name", "ensemble", "event_date", "active"]
|
||||||
|
list_filter = ["ensemble", "active"]
|
||||||
inlines = [ModuleInline]
|
inlines = [ModuleInline]
|
||||||
|
|
||||||
|
|
||||||
class ResourceAdmin(admin.ModelAdmin):
|
class ResourceAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name', 'media_type', 'project']
|
list_display = ["name", "media_type", "project"]
|
||||||
list_filter = ['project']
|
list_filter = ["project"]
|
||||||
|
|
||||||
|
|
||||||
class WikiPageAdmin(admin.ModelAdmin):
|
class WikiPageAdmin(admin.ModelAdmin):
|
||||||
list_display = ['title', 'project']
|
list_display = ["title", "project"]
|
||||||
list_filter = ['project']
|
list_filter = ["project"]
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.Ensemble, EnsembleAdmin)
|
admin.site.register(models.Ensemble, EnsembleAdmin)
|
||||||
admin.site.register(models.Project, ProjectAdmin)
|
admin.site.register(models.Project, ProjectAdmin)
|
||||||
admin.site.register(models.Resource, ResourceAdmin)
|
admin.site.register(models.Resource, ResourceAdmin)
|
||||||
admin.site.register(models.WikiPage, WikiPageAdmin)
|
admin.site.register(models.WikiPage, WikiPageAdmin)
|
||||||
|
|||||||
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceConfig(AppConfig):
|
class InterfaceConfig(AppConfig):
|
||||||
name = 'interface'
|
name = "interface"
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from crispy_forms.layout import Field
|
from crispy_forms.layout import Field
|
||||||
|
|
||||||
|
|
||||||
class BulmaFileUpload(Field):
|
class BulmaFileUpload(Field):
|
||||||
template = 'bulma/file_upload.html'
|
template = "bulma/file_upload.html"
|
||||||
|
|||||||
@ -5,58 +5,72 @@ from crispy_bulma.layout import FormGroup
|
|||||||
|
|
||||||
from . import models, fields
|
from . import models, fields
|
||||||
|
|
||||||
|
|
||||||
class BaseForm(forms.Form):
|
class BaseForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.helper = self.get_form_helper()
|
self.helper = self.get_form_helper()
|
||||||
|
|
||||||
def get_form_helper(self):
|
def get_form_helper(self):
|
||||||
helper = FormHelper(self)
|
helper = FormHelper(self)
|
||||||
#helper.add_input(Submit('submit', 'Submit', css_class='button is-link'))
|
# helper.add_input(Submit('submit', 'Submit', css_class='button is-link'))
|
||||||
#helper.layout.subm append(HTML('<a class="button is-light">Cancel</a>'))
|
# helper.layout.subm append(HTML('<a class="button is-light">Cancel</a>'))
|
||||||
#print(helper.layout)
|
# print(helper.layout)
|
||||||
helper.layout.append(FormGroup(
|
helper.layout.append(
|
||||||
Submit('submit', 'Save', css_class="button is-primary"),
|
FormGroup(
|
||||||
HTML('{% if view.cancel_url %}<div class="control"><a href="{{ view.cancel_url }}" class="button is-light">Cancel</a></div>{% endif %}')
|
Submit("submit", "Save", css_class="button is-primary"),
|
||||||
))
|
HTML(
|
||||||
|
'{% if view.cancel_url %}<div class="control"><a href="{{ view.cancel_url }}" class="button is-light">Cancel</a></div>{% endif %}'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(forms.ModelForm, BaseForm):
|
class ProjectForm(forms.ModelForm, BaseForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Project
|
model = models.Project
|
||||||
fields = ['name', 'description', 'modules', 'event_date']
|
fields = ["name", "description", "modules", "event_date"]
|
||||||
#widgets = {
|
# widgets = {
|
||||||
# 'event_date': forms.DateTimeInput(attrs={'type': 'date'})
|
# 'event_date': forms.DateTimeInput(attrs={'type': 'date'})
|
||||||
#}
|
# }
|
||||||
|
|
||||||
|
modules = forms.MultipleChoiceField(
|
||||||
|
choices=[(x, x.title()) for x in models.settings.POLYPHONIC_MODULES],
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
modules = forms.MultipleChoiceField(choices=[ (x, x.title()) for x in models.settings.POLYPHONIC_MODULES ],
|
|
||||||
widget=forms.CheckboxSelectMultiple, required=False)
|
|
||||||
|
|
||||||
class ResourceForm(forms.ModelForm, BaseForm):
|
class ResourceForm(forms.ModelForm, BaseForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Resource
|
model = models.Resource
|
||||||
fields = ['name', 'media_type', 'description', 'file']
|
fields = ["name", "media_type", "description", "file"]
|
||||||
|
|
||||||
def get_form_helper(self):
|
def get_form_helper(self):
|
||||||
helper = super().get_form_helper()
|
helper = super().get_form_helper()
|
||||||
helper[3].wrap(fields.BulmaFileUpload)
|
helper[3].wrap(fields.BulmaFileUpload)
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
class WikiForm(forms.ModelForm, BaseForm):
|
|
||||||
|
|
||||||
|
class WikiForm(forms.ModelForm, BaseForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.WikiPage
|
model = models.WikiPage
|
||||||
fields = ['title', 'markdown']
|
fields = ["title", "markdown"]
|
||||||
|
|
||||||
|
|
||||||
class CodeForm(BaseForm):
|
class CodeForm(BaseForm):
|
||||||
code = forms.CharField(max_length=14,
|
code = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={'placeholder': 'xxx-xxx-xxx', 'inputmode': 'numeric'}))
|
max_length=14,
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={"placeholder": "xxx-xxx-xxx", "inputmode": "numeric"}
|
||||||
|
),
|
||||||
|
)
|
||||||
passphrase = forms.CharField(max_length=32)
|
passphrase = forms.CharField(max_length=32)
|
||||||
|
|
||||||
|
|
||||||
class ResourceUploadForm(forms.Form):
|
class ResourceUploadForm(forms.Form):
|
||||||
pass
|
pass
|
||||||
# file = S3UploadField()
|
|
||||||
|
|
||||||
|
# file = S3UploadField()
|
||||||
|
|||||||
@ -14,11 +14,12 @@ import os.path
|
|||||||
from .utils import sign_data
|
from .utils import sign_data
|
||||||
|
|
||||||
MEDIA_TYPES = [
|
MEDIA_TYPES = [
|
||||||
('audio', "Audio"),
|
("audio", "Audio"),
|
||||||
('video', "Video"),
|
("video", "Video"),
|
||||||
('general', "General"),
|
("general", "General"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def rough_date(d):
|
def rough_date(d):
|
||||||
if not d:
|
if not d:
|
||||||
return False, "sometime..."
|
return False, "sometime..."
|
||||||
@ -27,53 +28,62 @@ def rough_date(d):
|
|||||||
if in_past:
|
if in_past:
|
||||||
days = abs(days)
|
days = abs(days)
|
||||||
if days == 0:
|
if days == 0:
|
||||||
m = int((d-timezone.now()).seconds/60)
|
m = int((d - timezone.now()).seconds / 60)
|
||||||
if m > 60:
|
if m > 60:
|
||||||
return in_past, "{0:d} hours".format(int(m / 60))
|
return in_past, "{0:d} hours".format(int(m / 60))
|
||||||
return in_past, "{0:d} minutes!".format(int(m % 60))
|
return in_past, "{0:d} minutes!".format(int(m % 60))
|
||||||
if days >= 14:
|
if days >= 14:
|
||||||
return in_past, "{0:d} weeks".format(int(days/7))
|
return in_past, "{0:d} weeks".format(int(days / 7))
|
||||||
if days >= 7:
|
if days >= 7:
|
||||||
return in_past, "{0:d} weeks, {1:d} days".format(int(days / 7), int(days % 7))
|
return in_past, "{0:d} weeks, {1:d} days".format(int(days / 7), int(days % 7))
|
||||||
return in_past, f"{days} days"
|
return in_past, f"{days} days"
|
||||||
|
|
||||||
|
|
||||||
def generate_code(length=9):
|
def generate_code(length=9):
|
||||||
return "".join([ random.choice('0123456789') for _ in range(length) ])
|
return "".join([random.choice("0123456789") for _ in range(length)])
|
||||||
|
|
||||||
|
|
||||||
class EnsembleQuerySet(models.QuerySet):
|
class EnsembleQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def for_user(self, user, ensemble_keys=[], project_keys=[]):
|
def for_user(self, user, ensemble_keys=[], project_keys=[]):
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
f = models.Q(slug__in=ensemble_keys) | models.Q(projects__in=project_keys)
|
f = models.Q(slug__in=ensemble_keys) | models.Q(projects__in=project_keys)
|
||||||
|
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
f |= models.Q(admins=user.pk)
|
f |= models.Q(admins=user.pk)
|
||||||
|
|
||||||
return self.filter(f).distinct()
|
return self.filter(f).distinct()
|
||||||
|
|
||||||
|
|
||||||
class Ensemble(models.Model):
|
class Ensemble(models.Model):
|
||||||
''' A group that plays together
|
"""A group that plays together"""
|
||||||
|
|
||||||
'''
|
name = models.CharField(max_length=100, help_text="Display name")
|
||||||
name = models.CharField(max_length=100,
|
slug = models.SlugField(
|
||||||
help_text="Display name")
|
max_length=100,
|
||||||
slug = models.SlugField(max_length=100, editable=False, unique=True,
|
editable=False,
|
||||||
help_text="Short name for the ensemble - used for folders")
|
unique=True,
|
||||||
admins = models.ManyToManyField('auth.User', related_name='ensembles')
|
help_text="Short name for the ensemble - used for folders",
|
||||||
details = models.TextField(blank=True,
|
)
|
||||||
help_text="Description of the ensemble (markdown)")
|
admins = models.ManyToManyField("auth.User", related_name="ensembles")
|
||||||
storage = models.ForeignKey('byostorage.UserStorage', null=True, on_delete=models.SET_NULL,
|
details = models.TextField(
|
||||||
help_text="Default storage for this ensemble")
|
blank=True, help_text="Description of the ensemble (markdown)"
|
||||||
nonce = models.SmallIntegerField(default=1,
|
)
|
||||||
help_text="Increment this to reset the authentication links")
|
storage = models.ForeignKey(
|
||||||
|
"byostorage.UserStorage",
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
help_text="Default storage for this ensemble",
|
||||||
|
)
|
||||||
|
nonce = models.SmallIntegerField(
|
||||||
|
default=1, help_text="Increment this to reset the authentication links"
|
||||||
|
)
|
||||||
|
|
||||||
objects = EnsembleQuerySet.as_manager()
|
objects = EnsembleQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('slug', )
|
ordering = ("slug",)
|
||||||
|
|
||||||
def active_projects(self):
|
def active_projects(self):
|
||||||
return self.projects.active().current()
|
return self.projects.active().current()
|
||||||
@ -83,7 +93,7 @@ class Ensemble(models.Model):
|
|||||||
return False
|
return False
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return True
|
return True
|
||||||
return user.pk in self.admins.values_list('pk', flat=True)
|
return user.pk in self.admins.values_list("pk", flat=True)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
@ -91,49 +101,56 @@ class Ensemble(models.Model):
|
|||||||
super(Ensemble, self).save(**kwargs)
|
super(Ensemble, self).save(**kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return resolve_url('ensemble_detail', ensemble=self.slug)
|
return resolve_url("ensemble_detail", ensemble=self.slug)
|
||||||
|
|
||||||
def auth(self):
|
def auth(self):
|
||||||
return sign_data(f'{self.pk}-{self.nonce}', 12)
|
return sign_data(f"{self.pk}-{self.nonce}", 12)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class ProjectQuerySet(models.QuerySet):
|
class ProjectQuerySet(models.QuerySet):
|
||||||
def current(self):
|
def current(self):
|
||||||
return self.filter(models.Q(event_date__gte=(timezone.now()-timezone.timedelta(7))) | models.Q(event_date=None))
|
return self.filter(
|
||||||
|
models.Q(event_date__gte=(timezone.now() - timezone.timedelta(7)))
|
||||||
|
| models.Q(event_date=None)
|
||||||
|
)
|
||||||
|
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.filter(active=True)
|
return self.filter(active=True)
|
||||||
|
|
||||||
def for_user(self, user, project_keys=[], ensemble_keys=[]):
|
def for_user(self, user, project_keys=[], ensemble_keys=[]):
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
f = models.Q(pk__in=project_keys) | models.Q(ensemble__slug__in=ensemble_keys)
|
f = models.Q(pk__in=project_keys) | models.Q(ensemble__slug__in=ensemble_keys)
|
||||||
|
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
f |= models.Q(ensemble__admins=user.pk)
|
f |= models.Q(ensemble__admins=user.pk)
|
||||||
|
|
||||||
return self.filter(f)
|
return self.filter(f)
|
||||||
|
|
||||||
|
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
''' A Project linked to an ensemble
|
"""A Project linked to an ensemble"""
|
||||||
'''
|
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
ensemble = models.ForeignKey(Ensemble, related_name='projects', on_delete=models.CASCADE, null=True)
|
ensemble = models.ForeignKey(
|
||||||
description = models.TextField(blank=True,
|
Ensemble, related_name="projects", on_delete=models.CASCADE, null=True
|
||||||
help_text="Markdown format")
|
)
|
||||||
|
description = models.TextField(blank=True, help_text="Markdown format")
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
event_date =models.DateTimeField(null=True, blank=True)
|
event_date = models.DateTimeField(null=True, blank=True)
|
||||||
owner = models.CharField(max_length=255, blank=True)
|
owner = models.CharField(max_length=255, blank=True)
|
||||||
nonce = models.SmallIntegerField(default=1,
|
nonce = models.SmallIntegerField(
|
||||||
help_text="Increment this to reset the authentication links")
|
default=1, help_text="Increment this to reset the authentication links"
|
||||||
|
)
|
||||||
|
|
||||||
objects = ProjectQuerySet.as_manager()
|
objects = ProjectQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['active', 'event_date']
|
ordering = ["active", "event_date"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def days(self):
|
def days(self):
|
||||||
@ -162,62 +179,74 @@ class Project(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def active_modules(self):
|
def active_modules(self):
|
||||||
return self.modules.values_list('name', flat=True)
|
return self.modules.values_list("name", flat=True)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return resolve_url('project_detail', project=self.pk)
|
return resolve_url("project_detail", project=self.pk)
|
||||||
|
|
||||||
def auth(self):
|
def auth(self):
|
||||||
return sign_data(f'{self.pk}-{self.nonce}', 12)
|
return sign_data(f"{self.pk}-{self.nonce}", 12)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Module(models.Model):
|
class Module(models.Model):
|
||||||
''' Enable modules on a oriject
|
"""Enable modules on a oriject"""
|
||||||
'''
|
|
||||||
name = models.SlugField(max_length=20, choices=[ (x, x.title()) for x in settings.POLYPHONIC_MODULES ])
|
name = models.SlugField(
|
||||||
project = models.ForeignKey(Project, related_name="modules", on_delete=models.CASCADE)
|
max_length=20, choices=[(x, x.title()) for x in settings.POLYPHONIC_MODULES]
|
||||||
|
)
|
||||||
|
project = models.ForeignKey(
|
||||||
|
Project, related_name="modules", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
def resource_key(resource, filename):
|
def resource_key(resource, filename):
|
||||||
return f'{resource.project.folder}/resources/{filename}'
|
return f"{resource.project.folder}/resources/{filename}"
|
||||||
|
|
||||||
|
|
||||||
class Resource(models.Model):
|
class Resource(models.Model):
|
||||||
''' A viewable file resource attached to a project
|
"""A viewable file resource attached to a project
|
||||||
|
|
||||||
e.g PDF instructions, MP3 backing track
|
e.g PDF instructions, MP3 backing track
|
||||||
'''
|
"""
|
||||||
project = models.ForeignKey(Project, related_name='resources', on_delete=models.CASCADE)
|
|
||||||
|
project = models.ForeignKey(
|
||||||
|
Project, related_name="resources", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
file = models.FileField(storage=BYOStorage(), upload_to=resource_key)
|
file = models.FileField(storage=BYOStorage(), upload_to=resource_key)
|
||||||
media_type = models.CharField(max_length=10, choices=MEDIA_TYPES, default='*')
|
media_type = models.CharField(max_length=10, choices=MEDIA_TYPES, default="*")
|
||||||
visible = models.BooleanField(default=True)
|
visible = models.BooleanField(default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-visible', '-pk']
|
ordering = ["-visible", "-pk"]
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if self.media_type == 'general':
|
if self.media_type == "general":
|
||||||
return ".*"
|
return ".*"
|
||||||
return f"{self.media_type}/*"
|
return f"{self.media_type}/*"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class WikiPage(models.Model):
|
class WikiPage(models.Model):
|
||||||
''' An editable wiki page for the project in markdown format
|
"""An editable wiki page for the project in markdown format"""
|
||||||
|
|
||||||
'''
|
project = models.ForeignKey(
|
||||||
project = models.ForeignKey(Project, related_name='wiki_pages', on_delete=models.CASCADE)
|
Project, related_name="wiki_pages", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
markdown = models.TextField()
|
markdown = models.TextField()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return resolve_url('wiki', project=self.project_id, pk=self.pk)
|
return resolve_url("wiki", project=self.project_id, pk=self.pk)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import os.path
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
def basename(value):
|
def basename(value):
|
||||||
return os.path.basename(value)
|
return os.path.basename(value)
|
||||||
|
|
||||||
register.filter('basename', basename)
|
|
||||||
|
register.filter("basename", basename)
|
||||||
|
|||||||
@ -3,12 +3,16 @@ from django.utils import timesince
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
def roughtimesince(value):
|
def roughtimesince(value):
|
||||||
return timesince.timesince(value, depth=1)
|
return timesince.timesince(value, depth=1)
|
||||||
|
|
||||||
register.filter('roughtimesince', roughtimesince)
|
|
||||||
|
register.filter("roughtimesince", roughtimesince)
|
||||||
|
|
||||||
|
|
||||||
def roughtimeuntil(value):
|
def roughtimeuntil(value):
|
||||||
return timesince.timeuntil(value, depth=1)
|
return timesince.timeuntil(value, depth=1)
|
||||||
|
|
||||||
register.filter('roughtimeuntil', roughtimeuntil)
|
|
||||||
|
register.filter("roughtimeuntil", roughtimeuntil)
|
||||||
|
|||||||
@ -2,9 +2,10 @@ from django import template
|
|||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def url_update(context, **kwargs):
|
def url_update(context, **kwargs):
|
||||||
params = context.request.GET.copy()
|
params = context.request.GET.copy()
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
params[k] = kwargs[k]
|
params[k] = kwargs[k]
|
||||||
return "?" + params.urlencode()
|
return "?" + params.urlencode()
|
||||||
|
|||||||
@ -4,8 +4,8 @@ from django.contrib.auth.models import User
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
class AccessTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class AccessTestCase(TestCase):
|
||||||
USERS = ()
|
USERS = ()
|
||||||
|
|
||||||
ENSEMBLES = ()
|
ENSEMBLES = ()
|
||||||
@ -19,13 +19,13 @@ class AccessTestCase(TestCase):
|
|||||||
|
|
||||||
cls.users = {}
|
cls.users = {}
|
||||||
for details in cls.USERS:
|
for details in cls.USERS:
|
||||||
cls.users[details['username']] = User.objects.create_user(**details)
|
cls.users[details["username"]] = User.objects.create_user(**details)
|
||||||
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
cls.ensembles = {}
|
cls.ensembles = {}
|
||||||
for details in cls.ENSEMBLES:
|
for details in cls.ENSEMBLES:
|
||||||
admins = details.pop('admins', [])
|
admins = details.pop("admins", [])
|
||||||
obj = models.Ensemble.objects.create(**details)
|
obj = models.Ensemble.objects.create(**details)
|
||||||
for admin in admins:
|
for admin in admins:
|
||||||
obj.admins.add(cls.users[admin])
|
obj.admins.add(cls.users[admin])
|
||||||
@ -33,37 +33,41 @@ class AccessTestCase(TestCase):
|
|||||||
|
|
||||||
cls.projects = {}
|
cls.projects = {}
|
||||||
for details in cls.PROJECTS:
|
for details in cls.PROJECTS:
|
||||||
when = details.pop('when', 0)
|
when = details.pop("when", 0)
|
||||||
ensemble = details.pop('ensemble')
|
ensemble = details.pop("ensemble")
|
||||||
details['event_date'] = now + timedelta(days=when) if when else None
|
details["event_date"] = now + timedelta(days=when) if when else None
|
||||||
obj = cls.ensembles[ensemble].projects.create(**details)
|
obj = cls.ensembles[ensemble].projects.create(**details)
|
||||||
cls.projects[details['name']] = obj
|
cls.projects[details["name"]] = obj
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def test_protected_views(self):
|
def test_protected_views(self):
|
||||||
|
|
||||||
self.assertAccess({ x: False for x in self.PROTECTED_URLS })
|
self.assertAccess({x: False for x in self.PROTECTED_URLS})
|
||||||
|
|
||||||
if 'admin' in self.users:
|
if "admin" in self.users:
|
||||||
self.client.force_login(self.users['admin'])
|
self.client.force_login(self.users["admin"])
|
||||||
self.assertAccess({ x: True for x in self.PROTECTED_URLS })
|
self.assertAccess({x: True for x in self.PROTECTED_URLS})
|
||||||
|
|
||||||
def login(self, user, passwd):
|
def login(self, user, passwd):
|
||||||
response = self.client.post('/login', {'username': user, 'password': passwd})
|
response = self.client.post("/login", {"username": user, "password": passwd})
|
||||||
self.assertEqual(response.status_code, 302, f"Failed to login as {user}")
|
self.assertEqual(response.status_code, 302, f"Failed to login as {user}")
|
||||||
|
|
||||||
def authorize(self, model, **kwargs):
|
def authorize(self, model, **kwargs):
|
||||||
object = model.objects.get(**kwargs)
|
object = model.objects.get(**kwargs)
|
||||||
response = self.client.get(f'{object.get_absolute_url()}?auth={object.auth()}')
|
response = self.client.get(f"{object.get_absolute_url()}?auth={object.auth()}")
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def assertAccess(self, urls):
|
def assertAccess(self, urls):
|
||||||
for url, expected in urls.items():
|
for url, expected in urls.items():
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code == 200, expected, f"Expected {expected} for {url} (status: {response.status_code})")
|
self.assertEqual(
|
||||||
|
response.status_code == 200,
|
||||||
|
expected,
|
||||||
|
f"Expected {expected} for {url} (status: {response.status_code})",
|
||||||
|
)
|
||||||
|
|
||||||
def assertObjectList(self, response, expected, element='name'):
|
def assertObjectList(self, response, expected, element="name"):
|
||||||
self.assertEqual(response.status_code, 200, "No result returned")
|
self.assertEqual(response.status_code, 200, "No result returned")
|
||||||
objects = response.context['object_list'].values_list(element, flat=True)
|
objects = response.context["object_list"].values_list(element, flat=True)
|
||||||
self.assertEqual(list(objects), expected)
|
self.assertEqual(list(objects), expected)
|
||||||
|
|||||||
@ -5,179 +5,213 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
from . import AccessTestCase
|
from . import AccessTestCase
|
||||||
|
|
||||||
class InterfaceAccessTestCase(AccessTestCase):
|
|
||||||
|
|
||||||
|
class InterfaceAccessTestCase(AccessTestCase):
|
||||||
USERS = (
|
USERS = (
|
||||||
{'username': 'admin', 'password': 'secret', 'is_superuser': True, 'is_staff': True},
|
{
|
||||||
{'username': 'homer', 'password': 'maggie'},
|
"username": "admin",
|
||||||
|
"password": "secret",
|
||||||
|
"is_superuser": True,
|
||||||
|
"is_staff": True,
|
||||||
|
},
|
||||||
|
{"username": "homer", "password": "maggie"},
|
||||||
)
|
)
|
||||||
|
|
||||||
ENSEMBLES = (
|
ENSEMBLES = (
|
||||||
{'name': 'The Be Sharps', 'slug': 'be-sharps', 'admins': ['homer']},
|
{"name": "The Be Sharps", "slug": "be-sharps", "admins": ["homer"]},
|
||||||
{'name': 'Lisa & the Bleeding Gums', 'slug': 'bleeding-gums'},
|
{"name": "Lisa & the Bleeding Gums", "slug": "bleeding-gums"},
|
||||||
{'name': 'Party Posse'},
|
{"name": "Party Posse"},
|
||||||
)
|
)
|
||||||
|
|
||||||
PROJECTS = (
|
PROJECTS = (
|
||||||
{'name': 'Baker St', 'ensemble': 'bleeding-gums', 'when': -12},
|
{"name": "Baker St", "ensemble": "bleeding-gums", "when": -12},
|
||||||
{'name': 'Navy Recruitment Day', 'ensemble': 'party-posse', 'when': 6},
|
{"name": "Navy Recruitment Day", "ensemble": "party-posse", "when": 6},
|
||||||
{'name': 'Barbershop Contest', 'ensemble': 'be-sharps', 'when': 28},
|
{"name": "Barbershop Contest", "ensemble": "be-sharps", "when": 28},
|
||||||
{'name': 'Open Mic Night', 'ensemble': 'bleeding-gums', 'when': 1 },
|
{"name": "Open Mic Night", "ensemble": "bleeding-gums", "when": 1},
|
||||||
{'name': 'Current Repertoire', 'ensemble': 'be-sharps'},
|
{"name": "Current Repertoire", "ensemble": "be-sharps"},
|
||||||
)
|
)
|
||||||
|
|
||||||
PROTECTED_URLS = (
|
PROTECTED_URLS = (
|
||||||
'/ensembles/be-sharps',
|
"/ensembles/be-sharps",
|
||||||
'/ensembles/be-sharps/new-project',
|
"/ensembles/be-sharps/new-project",
|
||||||
|
"/projects/3",
|
||||||
'/projects/3',
|
"/projects/3/resources",
|
||||||
'/projects/3/resources',
|
"/projects/3/resources/add",
|
||||||
'/projects/3/resources/add',
|
"/admin/interface/ensemble/",
|
||||||
|
"/admin/interface/project/",
|
||||||
'/admin/interface/ensemble/',
|
"/admin/interface/resource/",
|
||||||
'/admin/interface/project/',
|
"/admin/interface/wikipage/",
|
||||||
'/admin/interface/resource/',
|
|
||||||
'/admin/interface/wikipage/',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_bad_login(self):
|
def test_bad_login(self):
|
||||||
with self.assertRaisesMessage(self.failureException, 'Failed to login as admin'):
|
with self.assertRaisesMessage(
|
||||||
self.login('admin', 'admin')
|
self.failureException, "Failed to login as admin"
|
||||||
|
):
|
||||||
|
self.login("admin", "admin")
|
||||||
|
|
||||||
def test_superuser_ensembles(self):
|
def test_superuser_ensembles(self):
|
||||||
self.login('admin', 'secret')
|
self.login("admin", "secret")
|
||||||
response = self.client.get('/ensembles')
|
response = self.client.get("/ensembles")
|
||||||
self.assertObjectList(response, ['The Be Sharps', 'Lisa & the Bleeding Gums', 'Party Posse'])
|
self.assertObjectList(
|
||||||
self.assertContains(response, 'Django Admin')
|
response, ["The Be Sharps", "Lisa & the Bleeding Gums", "Party Posse"]
|
||||||
|
)
|
||||||
|
self.assertContains(response, "Django Admin")
|
||||||
|
|
||||||
def test_superuser_ensemble_permissions(self):
|
def test_superuser_ensemble_permissions(self):
|
||||||
self.login('admin', 'secret')
|
self.login("admin", "secret")
|
||||||
response = self.client.get('/ensembles/party-posse')
|
response = self.client.get("/ensembles/party-posse")
|
||||||
self.assertTrue(response.context['request'].is_admin)
|
self.assertTrue(response.context["request"].is_admin)
|
||||||
self.assertContains(response, "Add project")
|
self.assertContains(response, "Add project")
|
||||||
self.assertAccess({
|
self.assertAccess(
|
||||||
'/ensembles/be-sharps': True,
|
{
|
||||||
'/ensembles/bleeding-gums': True,
|
"/ensembles/be-sharps": True,
|
||||||
'/ensembles/party-posse': True,
|
"/ensembles/bleeding-gums": True,
|
||||||
'/ensembles/unknown': False,
|
"/ensembles/party-posse": True,
|
||||||
'/ensembles/be-sharps/new-project': True,
|
"/ensembles/unknown": False,
|
||||||
})
|
"/ensembles/be-sharps/new-project": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_superuser_projects(self):
|
def test_superuser_projects(self):
|
||||||
self.login('admin', 'secret')
|
self.login("admin", "secret")
|
||||||
response = self.client.get('/projects')
|
response = self.client.get("/projects")
|
||||||
self.assertObjectList(response, ['Current Repertoire', 'Open Mic Night', 'Navy Recruitment Day', 'Barbershop Contest'])
|
self.assertObjectList(
|
||||||
|
response,
|
||||||
self.assertObjectList(self.client.get('/ensembles/bleeding-gums'), ['Open Mic Night'])
|
[
|
||||||
self.assertObjectList(self.client.get('/ensembles/bleeding-gums?inactive'), ['Open Mic Night', 'Baker St'])
|
"Current Repertoire",
|
||||||
|
"Open Mic Night",
|
||||||
|
"Navy Recruitment Day",
|
||||||
|
"Barbershop Contest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertObjectList(
|
||||||
|
self.client.get("/ensembles/bleeding-gums"), ["Open Mic Night"]
|
||||||
|
)
|
||||||
|
self.assertObjectList(
|
||||||
|
self.client.get("/ensembles/bleeding-gums?inactive"),
|
||||||
|
["Open Mic Night", "Baker St"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_user_ensembles(self):
|
def test_user_ensembles(self):
|
||||||
self.login('homer', 'maggie')
|
self.login("homer", "maggie")
|
||||||
response = self.client.get('/ensembles')
|
response = self.client.get("/ensembles")
|
||||||
self.assertObjectList(response, ['The Be Sharps'])
|
self.assertObjectList(response, ["The Be Sharps"])
|
||||||
|
|
||||||
self.assertNotContains(response, 'Django Admin')
|
self.assertNotContains(response, "Django Admin")
|
||||||
|
|
||||||
def test_user_ensemble_permissions(self):
|
def test_user_ensemble_permissions(self):
|
||||||
self.login('homer', 'maggie')
|
self.login("homer", "maggie")
|
||||||
response = self.client.get('/ensembles/be-sharps')
|
response = self.client.get("/ensembles/be-sharps")
|
||||||
self.assertTrue(response.context['request'].is_admin)
|
self.assertTrue(response.context["request"].is_admin)
|
||||||
self.assertContains(response, "Add project")
|
self.assertContains(response, "Add project")
|
||||||
self.assertContains(response, 'Show all')
|
self.assertContains(response, "Show all")
|
||||||
self.assertAccess({
|
self.assertAccess(
|
||||||
'/ensembles/be-sharps': True,
|
{
|
||||||
'/ensembles/bleeding-gums': False,
|
"/ensembles/be-sharps": True,
|
||||||
'/ensembles/party-posse': False,
|
"/ensembles/bleeding-gums": False,
|
||||||
'/ensembles/be-sharps/new-project': True,
|
"/ensembles/party-posse": False,
|
||||||
'/ensembles/party-posse/new-project': False,
|
"/ensembles/be-sharps/new-project": True,
|
||||||
})
|
"/ensembles/party-posse/new-project": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.authorize(models.Ensemble, slug='bleeding-gums')
|
self.authorize(models.Ensemble, slug="bleeding-gums")
|
||||||
self.assertAccess({
|
self.assertAccess(
|
||||||
'/ensembles/be-sharps': True,
|
{
|
||||||
'/ensembles/bleeding-gums': True,
|
"/ensembles/be-sharps": True,
|
||||||
'/ensembles/party-posse': False,
|
"/ensembles/bleeding-gums": True,
|
||||||
'/ensembles/be-sharps/new-project': True,
|
"/ensembles/party-posse": False,
|
||||||
'/ensembles/party-posse/new-project': False,
|
"/ensembles/be-sharps/new-project": True,
|
||||||
})
|
"/ensembles/party-posse/new-project": False,
|
||||||
response = self.client.get('/ensembles/bleeding-gums')
|
}
|
||||||
self.assertFalse(response.context['request'].is_admin)
|
)
|
||||||
self.assertNotContains(response, 'Add project')
|
response = self.client.get("/ensembles/bleeding-gums")
|
||||||
self.assertNotContains(response, 'Show all')
|
self.assertFalse(response.context["request"].is_admin)
|
||||||
|
self.assertNotContains(response, "Add project")
|
||||||
|
self.assertNotContains(response, "Show all")
|
||||||
|
|
||||||
def test_user_projects(self):
|
def test_user_projects(self):
|
||||||
self.login('homer', 'maggie')
|
self.login("homer", "maggie")
|
||||||
response = self.client.get('/projects')
|
response = self.client.get("/projects")
|
||||||
self.assertObjectList(response, ['Current Repertoire', 'Barbershop Contest'])
|
self.assertObjectList(response, ["Current Repertoire", "Barbershop Contest"])
|
||||||
response = self.client.get('/projects/3')
|
response = self.client.get("/projects/3")
|
||||||
self.assertTrue(response.context['request'].is_admin)
|
self.assertTrue(response.context["request"].is_admin)
|
||||||
|
|
||||||
|
self.assertAccess(
|
||||||
self.assertAccess({
|
{
|
||||||
'/projects/3': True,
|
"/projects/3": True,
|
||||||
'/projects/3/resources': True,
|
"/projects/3/resources": True,
|
||||||
'/projects/3/resources/add': True,
|
"/projects/3/resources/add": True,
|
||||||
'/projects/4': False,
|
"/projects/4": False,
|
||||||
'/projects/4/resources': False,
|
"/projects/4/resources": False,
|
||||||
'/projects/4/resources/add': False,
|
"/projects/4/resources/add": False,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.authorize(models.Project, pk=4)
|
self.authorize(models.Project, pk=4)
|
||||||
response = self.client.get('/projects')
|
response = self.client.get("/projects")
|
||||||
self.assertObjectList(response, ['Current Repertoire', 'Open Mic Night', 'Barbershop Contest'])
|
self.assertObjectList(
|
||||||
response = self.client.get('/projects/4')
|
response, ["Current Repertoire", "Open Mic Night", "Barbershop Contest"]
|
||||||
self.assertFalse(response.context['request'].is_admin)
|
)
|
||||||
|
response = self.client.get("/projects/4")
|
||||||
|
self.assertFalse(response.context["request"].is_admin)
|
||||||
|
|
||||||
def test_anon_ensembles(self):
|
def test_anon_ensembles(self):
|
||||||
response = self.client.get('/ensembles')
|
response = self.client.get("/ensembles")
|
||||||
self.assertObjectList(response, [])
|
self.assertObjectList(response, [])
|
||||||
self.assertContains(response, 'You don\'t currently have access to any ensembles')
|
self.assertContains(
|
||||||
|
response, "You don't currently have access to any ensembles"
|
||||||
|
)
|
||||||
|
|
||||||
def test_anon_authorized_ensemble(self):
|
def test_anon_authorized_ensemble(self):
|
||||||
self.authorize(models.Ensemble, slug='party-posse')
|
self.authorize(models.Ensemble, slug="party-posse")
|
||||||
response = self.client.get('/ensembles/party-posse')
|
response = self.client.get("/ensembles/party-posse")
|
||||||
self.assertContains(response, 'Party Posse')
|
self.assertContains(response, "Party Posse")
|
||||||
|
|
||||||
response = self.client.get('/ensembles')
|
response = self.client.get("/ensembles")
|
||||||
self.assertObjectList(response, ['Party Posse'])
|
self.assertObjectList(response, ["Party Posse"])
|
||||||
|
|
||||||
|
self.assertAccess(
|
||||||
|
{
|
||||||
|
"/ensembles/be-sharps": False,
|
||||||
|
"/ensembles/party-posse": True,
|
||||||
|
"/ensembles/bleeding-gums": False,
|
||||||
|
"/ensembles/unknown": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = self.client.get("/projects")
|
||||||
|
self.assertObjectList(response, ["Navy Recruitment Day"])
|
||||||
|
|
||||||
self.assertAccess({
|
|
||||||
'/ensembles/be-sharps': False,
|
|
||||||
'/ensembles/party-posse': True,
|
|
||||||
'/ensembles/bleeding-gums': False,
|
|
||||||
'/ensembles/unknown': False,
|
|
||||||
})
|
|
||||||
response = self.client.get('/projects')
|
|
||||||
self.assertObjectList(response, ['Navy Recruitment Day'])
|
|
||||||
|
|
||||||
def test_anon_authorized_project(self):
|
def test_anon_authorized_project(self):
|
||||||
self.authorize(models.Project, pk=4)
|
self.authorize(models.Project, pk=4)
|
||||||
self.assertObjectList(self.client.get('/projects'), ['Open Mic Night'])
|
self.assertObjectList(self.client.get("/projects"), ["Open Mic Night"])
|
||||||
self.assertObjectList(self.client.get('/ensembles'), ['Lisa & the Bleeding Gums'])
|
self.assertObjectList(
|
||||||
|
self.client.get("/ensembles"), ["Lisa & the Bleeding Gums"]
|
||||||
|
)
|
||||||
|
|
||||||
self.assertAccess({
|
self.assertAccess(
|
||||||
'/projects/4': True,
|
{
|
||||||
'/projects/4/resources': True,
|
"/projects/4": True,
|
||||||
'/projects/1': False,
|
"/projects/4/resources": True,
|
||||||
'/projects/1/resources': False,
|
"/projects/1": False,
|
||||||
})
|
"/projects/1/resources": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_anon_permission_denied(self):
|
def test_anon_permission_denied(self):
|
||||||
self.assertAccess({
|
self.assertAccess(
|
||||||
'/ensembles': True,
|
{
|
||||||
'/ensembles/be-sharps': False,
|
"/ensembles": True,
|
||||||
'/ensembles/party-posse': False,
|
"/ensembles/be-sharps": False,
|
||||||
'/ensembles/bleeding-gums': False,
|
"/ensembles/party-posse": False,
|
||||||
'/ensembles/unknown': False,
|
"/ensembles/bleeding-gums": False,
|
||||||
})
|
"/ensembles/unknown": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_anon_deauthorize_project(self):
|
def test_anon_deauthorize_project(self):
|
||||||
self.authorize(models.Project, pk=4)
|
self.authorize(models.Project, pk=4)
|
||||||
self.assertAccess({
|
self.assertAccess({"/projects/4": True})
|
||||||
'/projects/4': True
|
|
||||||
})
|
|
||||||
models.Project.objects.filter(pk=4).update(nonce=2)
|
models.Project.objects.filter(pk=4).update(nonce=2)
|
||||||
self.assertAccess({
|
self.assertAccess({"/projects/4": False})
|
||||||
'/projects/4': False
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
class IntegrationTestCase(TestCase):
|
|
||||||
|
|
||||||
|
class IntegrationTestCase(TestCase):
|
||||||
def test_runs(self):
|
def test_runs(self):
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|||||||
@ -77,4 +77,3 @@ if settings.DEBUG:
|
|||||||
urlpatterns.append(
|
urlpatterns.append(
|
||||||
path("local_storage/<path:path>", serve, {"document_root": "local_storage"})
|
path("local_storage/<path:path>", serve, {"document_root": "local_storage"})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,10 @@ from django.core.exceptions import SuspiciousOperation
|
|||||||
signer = Signer()
|
signer = Signer()
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def sign_data(data, l=None):
|
def sign_data(data, l=None):
|
||||||
sig = signer.sign(data)
|
sig = signer.sign(data)
|
||||||
p = len(data) + 1
|
p = len(data) + 1
|
||||||
@ -14,6 +16,7 @@ def sign_data(data, l=None):
|
|||||||
l += p
|
l += p
|
||||||
return sig[p:l]
|
return sig[p:l]
|
||||||
|
|
||||||
|
|
||||||
def signed_url(name, **kwargs):
|
def signed_url(name, **kwargs):
|
||||||
"""
|
"""
|
||||||
>>> signed_url('foo/bar')
|
>>> signed_url('foo/bar')
|
||||||
@ -23,16 +26,19 @@ def signed_url(name, **kwargs):
|
|||||||
sep = "&" if "?" in url else "?"
|
sep = "&" if "?" in url else "?"
|
||||||
return sig.replace(":", f"{sep}auth=")
|
return sig.replace(":", f"{sep}auth=")
|
||||||
|
|
||||||
|
|
||||||
def check_signed_url(full_path):
|
def check_signed_url(full_path):
|
||||||
p = full_path.rfind('auth')
|
p = full_path.rfind("auth")
|
||||||
url = full_path[:p-1]
|
url = full_path[: p - 1]
|
||||||
logger.debug("check_signed_url: %s", url)
|
logger.debug("check_signed_url: %s", url)
|
||||||
signed = signed_url(url)
|
signed = signed_url(url)
|
||||||
if signed != full_path:
|
if signed != full_path:
|
||||||
logger.debug("Mismatch: %s != %s", full_path, signed)
|
logger.debug("Mismatch: %s != %s", full_path, signed)
|
||||||
signed = "_HIDDEN_"
|
signed = "_HIDDEN_"
|
||||||
raise SuspiciousOperation("Bad auth code")
|
raise SuspiciousOperation("Bad auth code")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
import doctest
|
import doctest
|
||||||
print(doctest.testmod())
|
|
||||||
|
print(doctest.testmod())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user