diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index fa8f2e7..22d0df7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -14,4 +14,4 @@ Developers * Raphael Kimmig (@RaphaelKimmig) * Andrew Ingram (@AndrewIngram) * Gregor Müllegger (@gregmuellegger) - +* Rivo Laks (@rivol) diff --git a/djadmin2/models.py b/djadmin2/models.py index 9b0e5b9..f18fe59 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -5,6 +5,8 @@ synonymous with the django.contrib.admin.sites model. """ from django.conf.urls import patterns, include, url +from django.contrib.auth import models as auth_app +from django.db.models import get_models, signals from djadmin2 import views @@ -44,24 +46,42 @@ class BaseAdmin2(object): readonly_fields = () ordering = None - def has_view_permission(self, request): - """ - Returns True if the given HttpRequest has permission to view - *at least one* page in the mongonaut site. - """ - return request.user.is_authenticated() and request.user.is_active - def has_edit_permission(self, request): + def __init__(self, model): + super(BaseAdmin2, self).__init__() + + self.model = model + + + def _user_has_permission(self, user, permission_type, obj=None): + """ Generic method for checking whether the user has permission of specified type for the model. + Type can be one of view, add, change, delete. + You can also specify instance of the model for object-specific permission check. + """ + if not user.is_authenticated() or not user.is_staff: + return False + opts = self.model._meta + full_permission_name = '%s.%s_%s' % (opts.app_label, permission_type, opts.object_name.lower()) + return user.has_perm(full_permission_name, obj) + + def has_permission(self, request, permission_type, obj=None): + return self._user_has_permission(request.user, permission_type, obj) + + def has_view_permission(self, request, obj=None): + """ Can view this object """ + return self.has_permission(request, 'view', obj) + + def has_edit_permission(self, request, obj=None): """ Can edit this object """ - return request.user.is_authenticated() and request.user.is_active and request.user.is_staff + return self.has_permission(request, 'change', obj) - def has_add_permission(self, request): + def has_add_permission(self, request, obj=None): """ Can add this object """ - return request.user.is_authenticated() and request.user.is_active and request.user.is_staff + return self.has_permission(request, 'add', obj) - def has_delete_permission(self, request): + def has_delete_permission(self, request, obj=None): """ Can delete this object """ - return request.user.is_authenticated() and request.user.is_active and request.user.is_superuser + return self.has_permission(request, 'delete', obj) class ModelAdmin2(BaseAdmin2): @@ -134,12 +154,12 @@ class ModelAdmin2(BaseAdmin2): ), url( regex=r'^create/$', - view=self.create_view.as_view(**self.get_create_kwargs()), + view=self.create_view.as_view(**self.get_create_kwargs()), name='create' ), url( regex=r'^(?P[0-9]+)/$', - view=self.detail_view.as_view(**self.get_detail_kwargs()), + view=self.detail_view.as_view(**self.get_detail_kwargs()), name='detail' ), url( @@ -158,3 +178,55 @@ class ModelAdmin2(BaseAdmin2): def urls(self): # We set the application and instance namespace here return self.get_urls(), None, None + + + +def create_permissions(app, created_models, verbosity, **kwargs): + """ + Creates 'view' permissions for all models. + django.contrib.auth only creates add, change and delete permissions. Since we also support read-only views, we need + to add our own extra permission. + Copied from django.contrib.auth.management.create_permissions + """ + from django.contrib.contenttypes.models import ContentType + + def _get_permission_codename(action, opts): + return u'%s_%s' % (action, opts.object_name.lower()) + + app_models = get_models(app) + + # This will hold the permissions we're looking for as + # (content_type, (codename, name)) + searched_perms = list() + # The codenames and ctypes that should exist. + ctypes = set() + for klass in app_models: + ctype = ContentType.objects.get_for_model(klass) + ctypes.add(ctype) + + opts = klass._meta + perm = (_get_permission_codename('view', opts), u'Can view %s' % opts.verbose_name_raw) + searched_perms.append((ctype, perm)) + + # Find all the Permissions that have a context_type for a model we're + # looking for. We don't need to check for codenames since we already have + # a list of the ones we're going to create. + all_perms = set(auth_app.Permission.objects.filter( + content_type__in=ctypes, + ).values_list( + "content_type", "codename" + )) + + objs = [ + auth_app.Permission(codename=codename, name=name, content_type=ctype) + for ctype, (codename, name) in searched_perms + if (ctype.pk, codename) not in all_perms + ] + auth_app.Permission.objects.bulk_create(objs) + if verbosity >= 2: + for obj in objs: + print "Adding permission '%s'" % obj + + +signals.post_syncdb.connect(create_permissions, + dispatch_uid = "django-admin2.djadmin2.models.create_permissions") diff --git a/djadmin2/templates/admin2/bootstrap/model_list.html b/djadmin2/templates/admin2/bootstrap/model_list.html index e71bd3b..8121584 100644 --- a/djadmin2/templates/admin2/bootstrap/model_list.html +++ b/djadmin2/templates/admin2/bootstrap/model_list.html @@ -1,11 +1,42 @@ {% extends "admin2/bootstrap/base.html" %} {% block content %} - add -
-{% for obj in object_list %} - {{ obj }} detail edit delete
-{% endfor %} +
+
+

Select {{ model }} to change

+
+
+ {% if has_add_permission %} + Add {{ model|title }} + {% endif %} +
+
+
+ +
+
+ + + + + + + {% for obj in object_list %} + + + {% endfor %} + +
{{ model|title}}
+ {{ obj }} detail + {% if has_edit_permission %} + edit + {% endif %} + {% if has_delete_permission %} + delete + {% endif %} +
+
+
{% endblock content %} diff --git a/djadmin2/views.py b/djadmin2/views.py index 63fd427..d0250ad 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -1,21 +1,52 @@ import os from django.conf import settings +from django.contrib.auth.views import redirect_to_login +from django.core.exceptions import PermissionDenied from django.forms.models import modelform_factory from django.views import generic from django.db import models -from braces.views import LoginRequiredMixin, StaffuserRequiredMixin +from braces.views import LoginRequiredMixin, StaffuserRequiredMixin, AccessMixin ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap") -class Admin2Mixin(object): - modeladmin = None +class Admin2Mixin(object): def get_template_names(self): return [os.path.join(ADMIN2_THEME_DIRECTORY, self.default_template_name)] + +class AdminModel2Mixin(Admin2Mixin, AccessMixin): + modeladmin = None + # Permission type to check for when a request is sent to this view. + permission_type = None + + def dispatch(self, request, *args, **kwargs): + # Check if user has necessary permissions. If the permission_type isn't specified then check for staff status. + print "distpatch perm check:", self.permission_type + has_permission = self.modeladmin.has_permission(request, self.permission_type) \ + if self.permission_type else request.user.is_staff + # Raise exception or redirect to login if user doesn't have permissions. + if not has_permission: + if self.raise_exception: + raise PermissionDenied # return a forbidden response + else: + return redirect_to_login(request.get_full_path(), + self.get_login_url(), self.get_redirect_field_name()) + + return super(AdminModel2Mixin, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(AdminModel2Mixin, self).get_context_data(**kwargs) + context.update({ + 'has_add_permission': self.modeladmin.has_add_permission(self.request), + 'has_edit_permission': self.modeladmin.has_edit_permission(self.request), + 'has_delete_permission': self.modeladmin.has_delete_permission(self.request), + }) + return context + def get_model(self): return self.model @@ -39,26 +70,37 @@ class IndexView(Admin2Mixin, generic.TemplateView): }) return data -class ModelListView(Admin2Mixin, generic.ListView): +class ModelListView(AdminModel2Mixin, generic.ListView): default_template_name = "model_list.html" + permission_type = 'view' + + def get_context_data(self, **kwargs): + context = super(ModelListView, self).get_context_data(**kwargs) + context['model'] = self.get_model()._meta.verbose_name + context['model_pluralized'] = self.get_model()._meta.verbose_name_plural + return context -class ModelDetailView(Admin2Mixin, generic.DetailView): +class ModelDetailView(AdminModel2Mixin, generic.DetailView): default_template_name = "model_detail.html" + permission_type = 'view' -class ModelEditFormView(Admin2Mixin, generic.UpdateView): +class ModelEditFormView(AdminModel2Mixin, generic.UpdateView): form_class = None success_url = "../../" default_template_name = "model_edit_form.html" + permission_type = 'change' -class ModelAddFormView(Admin2Mixin, generic.CreateView): +class ModelAddFormView(AdminModel2Mixin, generic.CreateView): form_class = None success_url = "../" default_template_name = "model_add_form.html" + permission_type = 'add' -class ModelDeleteView(Admin2Mixin, generic.DeleteView): +class ModelDeleteView(AdminModel2Mixin, generic.DeleteView): success_url = "../../" default_template_name = "model_delete.html" + permission_type = 'delete' diff --git a/docs/TODO b/docs/TODO deleted file mode 100644 index e69de29..0000000 diff --git a/docs/index.rst b/docs/index.rst index 1710cc4..f9b5cd7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,17 +12,24 @@ Our goal is to make this API work: .. code-block:: python - # myapp/admin2.py + # Import your custom models + from .models import Post, Comment + from django.contrib.auth.forms import UserCreationForm, UserChangeForm + from django.contrib.auth.models import User - # Import the Admin2 base class - from djadmin2.models import Admin2 + import djadmin2 + from djadmin2.models import ModelAdmin2 - # Import your custom models - from blog.models import Post - # Instantiate the Admin2 class - # Then attach the admin2 object to your model - Post.admin2 = Admin2() + class UserAdmin2(ModelAdmin2): + create_form_class = UserCreationForm + update_form_class = UserChangeForm + + + # Register each model with the admin + djadmin2.default.register(Post) + djadmin2.default.register(Comment) + djadmin2.default.register(User, UserAdmin2) .. _GitHub: https://github.com/pydanny/django-admin2 diff --git a/example/example/urls.py b/example/example/urls.py index d5e661f..34f1f72 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -8,12 +8,6 @@ import djadmin2 djadmin2.default.autodiscover() urlpatterns = patterns('', - # Examples: url(r'^admin2/', include(djadmin2.default.urls)), - # url(r'^example/', include('example.foo.urls')), - - # Uncomment the admin/doc line below to enable admin documentation: - # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^admin/', include(admin.site.urls)), )