from django import newforms as forms from django.utils.encoding import force_unicode from django.contrib.admin.options import ModelAdmin from django.db.models.fields import NOT_PROVIDED from decimal import Decimal from django.template.defaultfilters import linebreaksbr from django.template.defaultfilters import truncatewords_html DEBUG = False class ReadOnlyTextWidget(forms.widgets.Widget): """ This is improved DefaultValueWidget from http://www.djangosnippets.org/snippets/323/ """ def __init__(self, value, display=None, attrs=None, fk=False): #if isinstance(display.widget, label): #raise Exception(display.widget.__dict__) if DEBUG: print '[i]', display, value # this allows to genericly pass in any field object intending to # catch ModelChoiceFields, without having to care about the actual # type. if isinstance(display, forms.Field): self.display = display else: self.display = None self.value = value super(ReadOnlyTextWidget, self).__init__(attrs) #def _has_changed(self, initial, data): #print '[H]', initial, data #return False def value_from_datadict(self, data, files, name): value = data.get(name, self.value) if DEBUG: print "[d] value for %s = %s" % (name, value) #HACK: Decimal can't be converted to Decimal again # so returning str instead if type(value) is Decimal: return str(value) if isinstance(value, forms.Field): return str(value) return value def render(self, name, value, attrs=None): if DEBUG: print "[r] value for %s = %s" % (name, value) if isinstance(self.display, forms.ModelChoiceField): try: value = self.display.queryset.get(pk=value) except: value = value if isinstance(value, list): r = ",".join(map(unicode, self.value)) else: r = value s = force_unicode(r, strings_only=False) return truncatewords_html(linebreaksbr(s), 50) class ReadOnlyTextWidget(forms.widgets.Widget): """ This is improved DefaultValueWidget from http://www.djangosnippets.org/snippets/323/ """ def __init__(self, value, display=None, attrs=None, fk=False): #if isinstance(display.widget, label): #raise Exception(display.widget.__dict__) if isinstance(display, forms.ModelChoiceField): try: self.display = display.queryset.get(pk=value) except: self.display = value # this allows to genericly pass in any field object intending to # catch ModelChoiceFields, without having to care about the actual # type. elif isinstance(display, forms.Field): self.display = None else: self.display = display self.value = value super(ReadOnlyTextWidget, self).__init__(attrs) def value_from_datadict(self, data, files, name): value = data.get(name, self.value) if DEBUG: print "[d] value for %s = %s" % (name, value) #HACK: Decimal can't be converted to Decimal again # so returning str instead if type(value) is Decimal: return str(value) if isinstance(value, forms.Field): return str(value) return value def render(self, name, value, attrs=None): if self.display is None: r = self.value elif isinstance(self.display, list): r = ",".join(map(unicode, self.display)) else: r = self.display s = force_unicode(r, strings_only=False) return truncatewords_html(linebreaksbr(s), 50) class FieldLevelPermissionsAdmin(ModelAdmin): def get_fieldsets(self, request, obj): "Hook for specifying fieldsets for the add form." if self.declared_fieldsets: fieldsets = self.declared_fieldsets else: form = self.get_form(request, obj) fieldsets = [(None, {'fields': form.base_fields.keys()})] for fs in fieldsets: fs[1]['fields'] = [f for f in fs[1]['fields'] if self.can_view_field(request, obj, f)] return fieldsets def get_form(self, request, obj=None): superclass = super(RowLevelPermissionsAdmin, self) formclass = superclass.get_form(request, obj) for name, field in formclass.base_fields.items(): if request.method == 'POST' and not self.can_change_field(request, obj, name): del formclass.base_fields[name] elif not self.can_view_field(request, obj, name): del formclass.base_fields[name] return formclass #XXX: To be overriden in child def can_view_field(self, request, object, field_name): """ Boolean method, returning True if user allowed to view field with name field_name. user is stored in the request object, object is None only if object does not exist yet """ if request.user.is_superuser: return True if object is None: return request.user.has_add_permission(request) else: return request.user.has_change_permission(request, object) #XXX: To be overriden in child def can_change_field(self, request, object, field_name): """ Boolean method, returning True if user allowed to change field with name field_name. user is stored in the request object, object is None only if object does not exist yet """ if self.can_view_field(request, object, field_name): return True return False def formfield_for_dbfield(self, db_field, **kwargs): superclass = super(RowLevelPermissionsAdmin, self) field = superclass.formfield_for_dbfield(db_field, **kwargs) if not field: return None default_value = kwargs.get('initial', db_field.default) if default_value is NOT_PROVIDED: default_value = None if not self.can_view_field(self._request, self._object, db_field.name): #XXX: Not displayed, but used when default value is provided #if default_value: field.widget = ReadOnlyTextWidget(default_value, display=field, fk=True) #else: # return None #return None elif not self.can_change_field(self._request, self._object, db_field.name): #XXX: Displaying as text #return None field.widget = ReadOnlyTextWidget(default_value, display=field) return field def has_add_permission(self, request): perm_name = self.opts.app_label + '.' + self.opts.get_add_permission() return request.user.has_perm(perm_name) def has_change_permission(self, request, obj=None): perm_name = self.opts.app_label + '.' + self.opts.get_change_permission() if not request.user.has_perm(perm_name): return False if obj: #TODO: Remove this extra query if not self.queryset(request).filter(pk=obj.id).count(): return False return True def changelist_view(self, request): # useful for list_display customization self._action = 'changelist' self._request = request self._object = None superclass = super(RowLevelPermissionsAdmin, self) return superclass.changelist_view(request) def change_view(self, request, object_id): # assignments are required for permission checks later self._action = 'change' model = self.model try: #TODO: Get rid of this query (lines copied from parent) obj = model._default_manager.get(pk=object_id) except model.DoesNotExist: # Don't raise Http404 just yet, because we haven't checked # permissions yet. We don't want an unauthenticated user to be able # to determine whether a given object exists. obj = None self._object = obj self._request = request superclass = super(RowLevelPermissionsAdmin, self) return superclass.change_view(request, object_id) def add_view(self, request): # assignments are required for permission checks later self._action = 'add' self._object = None self._request = request superclass = super(RowLevelPermissionsAdmin, self) return superclass.add_view(request)