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