kopia lustrzana https://github.com/OpenDroneMap/WebODM
Added django-guardian, set owner permissions on post save, testing, CSS fixes
rodzic
d686e63c40
commit
90cc52ff2d
|
@ -1 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from guardian.admin import GuardedModelAdmin
|
||||||
|
from .models import Project
|
||||||
|
|
||||||
|
class ProjectAdmin(GuardedModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
admin.site.register(Project, ProjectAdmin)
|
|
@ -11,7 +11,7 @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|
||||||
class ProjectViewSet(viewsets.ModelViewSet):
|
class ProjectViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
Projects the current user has access to, including the ability to create new ones.
|
Projects the current user has access to.
|
||||||
"""
|
"""
|
||||||
queryset = models.Project.objects.all()
|
queryset = models.Project.objects.all()
|
||||||
serializer_class = ProjectSerializer
|
serializer_class = ProjectSerializer
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
import .signals
|
||||||
|
|
||||||
class MainConfig(AppConfig):
|
class MainConfig(AppConfig):
|
||||||
name = 'main'
|
name = 'main'
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
# password: test1234
|
# password: test1234
|
||||||
password: pbkdf2_sha256$30000$pFwIz88hEEZ4$siNaZefZB3bb0DlemWNOkj6+tCq3I3LW+IgVw5VsRmE=
|
password: pbkdf2_sha256$30000$pFwIz88hEEZ4$siNaZefZB3bb0DlemWNOkj6+tCq3I3LW+IgVw5VsRmE=
|
||||||
last_login: null
|
last_login: null
|
||||||
is_superuser: true
|
is_superuser: false
|
||||||
username: testuser2
|
username: testuser2
|
||||||
first_name: ''
|
first_name: ''
|
||||||
last_name: ''
|
last_name: ''
|
||||||
|
@ -29,4 +29,20 @@
|
||||||
is_active: true
|
is_active: true
|
||||||
date_joined: '2016-09-11T22:26:52.582858+00:00'
|
date_joined: '2016-09-11T22:26:52.582858+00:00'
|
||||||
groups: []
|
groups: []
|
||||||
|
user_permissions: []
|
||||||
|
- model: auth.user
|
||||||
|
pk: 3
|
||||||
|
fields:
|
||||||
|
# password: test1234
|
||||||
|
password: pbkdf2_sha256$30000$pFwIz88hEEZ4$siNaZefZB3bb0DlemWNOkj6+tCq3I3LW+IgVw5VsRmE=
|
||||||
|
last_login: null
|
||||||
|
is_superuser: false
|
||||||
|
username: testuser3
|
||||||
|
first_name: ''
|
||||||
|
last_name: ''
|
||||||
|
email: test2@mail.com
|
||||||
|
is_staff: true
|
||||||
|
is_active: true
|
||||||
|
date_joined: '2016-09-11T22:26:52.582858+00:00'
|
||||||
|
groups: []
|
||||||
user_permissions: []
|
user_permissions: []
|
|
@ -1,10 +1,15 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import signals
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.postgres import fields
|
from django.contrib.postgres import fields
|
||||||
from nodeodm.models import ProcessingNode
|
from nodeodm.models import ProcessingNode
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from guardian.shortcuts import get_perms_for_model, assign_perm
|
||||||
|
from guardian.models import UserObjectPermissionBase
|
||||||
|
from guardian.models import GroupObjectPermissionBase
|
||||||
|
|
||||||
def assets_directory_path(taskId, projectId, filename):
|
def assets_directory_path(taskId, projectId, filename):
|
||||||
# files will be uploaded to MEDIA_ROOT/project_<id>/task_<id>/<filename>
|
# files will be uploaded to MEDIA_ROOT/project_<id>/task_<id>/<filename>
|
||||||
|
@ -14,12 +19,37 @@ def assets_directory_path(taskId, projectId, filename):
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
owner = models.ForeignKey(User, on_delete=models.PROTECT, help_text="The person who created the project")
|
owner = models.ForeignKey(User, on_delete=models.PROTECT, help_text="The person who created the project")
|
||||||
name = models.CharField(max_length=255, help_text="A label used to describe the project")
|
name = models.CharField(max_length=255, help_text="A label used to describe the project")
|
||||||
description = models.TextField(null=True, help_text="More in-depth description of the project")
|
description = models.TextField(null=True, blank=True, help_text="More in-depth description of the project")
|
||||||
created_at = models.DateTimeField(default=timezone.now, help_text="Creation date")
|
created_at = models.DateTimeField(default=timezone.now, help_text="Creation date")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = (
|
||||||
|
('view_project', 'Can view project'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(signals.post_save, sender=Project, dispatch_uid="project_post_save")
|
||||||
|
def project_post_save(sender, instance, created, **kwargs):
|
||||||
|
"""
|
||||||
|
Automatically assigns all permissions to the owner. If the owner changes
|
||||||
|
it's up to the user/developer to remove the previous owner's permissions.
|
||||||
|
"""
|
||||||
|
for perm in get_perms_for_model(sender).all():
|
||||||
|
assign_perm(perm.codename, instance.owner, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectUserObjectPermission(UserObjectPermissionBase):
|
||||||
|
content_object = models.ForeignKey(Project)
|
||||||
|
|
||||||
|
class ProjectGroupObjectPermission(GroupObjectPermissionBase):
|
||||||
|
content_object = models.ForeignKey(Project)
|
||||||
|
|
||||||
|
# from guardian.shortcuts import get_objects_for_user
|
||||||
|
# ...
|
||||||
|
# videos = get_objects_for_user(request.user, "view_video", Video.objects.all())
|
||||||
|
|
||||||
def gcp_directory_path(task, filename):
|
def gcp_directory_path(task, filename):
|
||||||
return assets_directory_path(task.id, task.project.id, filename)
|
return assets_directory_path(task.id, task.project.id, filename)
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
/* HEADER */
|
/* HEADER */
|
||||||
/* SIDEBAR */
|
/* SIDEBAR */
|
||||||
/* POPUP */ }
|
/* POPUP */ }
|
||||||
|
.admin-area #changelist-filter {
|
||||||
|
display: none; }
|
||||||
.admin-area a:link, .admin-area a:visited {
|
.admin-area a:link, .admin-area a:visited {
|
||||||
color: #447e9b;
|
color: #447e9b;
|
||||||
text-decoration: none; }
|
text-decoration: none; }
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.admin-area{
|
.admin-area{
|
||||||
|
#changelist-filter{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
a:link, a:visited {
|
a:link, a:visited {
|
||||||
color: #447e9b;
|
color: #447e9b;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
|
@ -72,8 +72,8 @@
|
||||||
.admin-area .colMS .aligned .vLargeTextField, .admin-area .colMS .aligned .vXMLLargeTextField {
|
.admin-area .colMS .aligned .vLargeTextField, .admin-area .colMS .aligned .vXMLLargeTextField {
|
||||||
width: 350px; }
|
width: 350px; }
|
||||||
.admin-area form .aligned ul {
|
.admin-area form .aligned ul {
|
||||||
margin-left: 160px;
|
margin-left: 6px;
|
||||||
padding-left: 10px; }
|
padding-left: 3px; }
|
||||||
.admin-area form .aligned ul.radiolist {
|
.admin-area form .aligned ul.radiolist {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -302,8 +302,8 @@
|
||||||
background-image: url(../img/search.svg); }
|
background-image: url(../img/search.svg); }
|
||||||
.admin-area form .related-widget-wrapper ul {
|
.admin-area form .related-widget-wrapper ul {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 0;
|
margin-left: 6px;
|
||||||
padding-left: 0; }
|
padding-left: 3px; }
|
||||||
.admin-area .clearable-file-input input {
|
.admin-area .clearable-file-input input {
|
||||||
margin-top: 0; }
|
margin-top: 0; }
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -105,8 +105,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
form .aligned ul {
|
form .aligned ul {
|
||||||
margin-left: 160px;
|
margin-left: 6px;
|
||||||
padding-left: 10px;
|
padding-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .aligned ul.radiolist {
|
form .aligned ul.radiolist {
|
||||||
|
@ -489,8 +489,8 @@
|
||||||
|
|
||||||
form .related-widget-wrapper ul {
|
form .related-widget-wrapper ul {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 0;
|
margin-left: 6px;
|
||||||
padding-left: 0;
|
padding-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearable-file-input input {
|
.clearable-file-input input {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
{% block extra-headers %}{% endblock %}
|
{% block extra-headers %}{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'app/css/main.css' %}" />
|
<link rel="stylesheet" type="text/css" href="{% static 'app/css/main.css' %}" />
|
||||||
<script src="{% static 'app/js/vendor/modernizr-2.8.3.min.js' %}"></script>
|
<script src="{% static 'app/js/vendor/modernizr-2.8.3.min.js' %}"></script>
|
||||||
|
<script src="{% static 'app/js/vendor/knockout-3.4.0.js' %}"></script>
|
||||||
<script src="{% static 'app/js/vendor/jquery-1.11.2.min.js' %}"></script>
|
<script src="{% static 'app/js/vendor/jquery-1.11.2.min.js' %}"></script>
|
||||||
<title>{{title|default:"Login"}} - WebODM</title>
|
<title>{{title|default:"Login"}} - WebODM</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
29
app/tests.py
29
app/tests.py
|
@ -84,3 +84,32 @@ class TestApp(TestCase):
|
||||||
|
|
||||||
res = c.get('/processingnode/abc/')
|
res = c.get('/processingnode/abc/')
|
||||||
self.assertTrue(res.status_code == 404)
|
self.assertTrue(res.status_code == 404)
|
||||||
|
|
||||||
|
def test_projects(self):
|
||||||
|
# Get a normal user
|
||||||
|
user = User.objects.get(pk=2)
|
||||||
|
self.assertFalse(user.is_superuser)
|
||||||
|
|
||||||
|
# Create a new project
|
||||||
|
p = Project(owner=user, name="test")
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
# Have the proper permissions been set?
|
||||||
|
self.assertTrue(user.has_perm("view_project", p))
|
||||||
|
self.assertTrue(user.has_perm("add_project", p))
|
||||||
|
self.assertTrue(user.has_perm("change_project", p))
|
||||||
|
self.assertTrue(user.has_perm("delete_project", p))
|
||||||
|
|
||||||
|
# Get a superuser
|
||||||
|
superUser = User.objects.get(pk=1)
|
||||||
|
self.assertTrue(superUser.is_superuser)
|
||||||
|
|
||||||
|
# He should also have permissions, although not explicitly set
|
||||||
|
self.assertTrue(superUser.has_perm("delete_project", p))
|
||||||
|
|
||||||
|
# Get another user
|
||||||
|
anotherUser = User.objects.get(pk=3)
|
||||||
|
self.assertFalse(anotherUser.is_superuser)
|
||||||
|
|
||||||
|
# Should not have permission
|
||||||
|
self.assertFalse(anotherUser.has_perm("delete_project", p))
|
||||||
|
|
|
@ -15,7 +15,7 @@ def dashboard(request):
|
||||||
no_processingnodes = ProcessingNode.objects.count() == 0
|
no_processingnodes = ProcessingNode.objects.count() == 0
|
||||||
|
|
||||||
# Create first project automatically
|
# Create first project automatically
|
||||||
if Project.objects.count() == 0:
|
if Project.objects.filter(owner=request.user).count() == 0:
|
||||||
proj = Project(owner=request.user, name=_("First Project"))
|
proj = Project(owner=request.user, name=_("First Project"))
|
||||||
proj.save()
|
proj.save()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ cryptography==1.5
|
||||||
Django==1.10
|
Django==1.10
|
||||||
django-common-helpers==0.8.0
|
django-common-helpers==0.8.0
|
||||||
django-filter==0.15.2
|
django-filter==0.15.2
|
||||||
|
django-guardian==1.4.6
|
||||||
|
django-knockout==0.3.2
|
||||||
djangorestframework==3.4.7
|
djangorestframework==3.4.7
|
||||||
enum34==1.1.6
|
enum34==1.1.6
|
||||||
fido==3.2.0
|
fido==3.2.0
|
||||||
|
|
|
@ -38,6 +38,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'guardian',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'app',
|
'app',
|
||||||
'nodeodm',
|
'nodeodm',
|
||||||
|
@ -111,6 +112,11 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Hook guardian
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'django.contrib.auth.backends.ModelBackend', # this is default
|
||||||
|
'guardian.backends.ObjectPermissionBackend',
|
||||||
|
)
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||||
|
|
Ładowanie…
Reference in New Issue