Modele tylko do odczytu w interfejsie administratora Django?

86

Jak mogę sprawić, by model był całkowicie tylko do odczytu w interfejsie administratora? Jest to rodzaj tabeli dziennika, w której używam funkcji administratora do wyszukiwania, sortowania, filtrowania itp., Ale nie ma potrzeby modyfikowania dziennika.

W przypadku, gdy wygląda to na duplikat, nie próbuję tego zrobić:

  • Nie szukam pól tylko do odczytu (nawet ustawienie każdego pola jako tylko do odczytu pozwoliłoby na tworzenie nowych rekordów)
  • Nie chcę tworzyć użytkownika tylko do odczytu : każdy użytkownik powinien być tylko do odczytu.
Steve Bennett
źródło
2
ta funkcja powinna być wkrótce dostępna: github.com/django/django/pull/5297
Bosco
2
has_view_permissionzostał ostatecznie zaimplementowany w Django 2.1. Zobacz także stackoverflow.com/a/51641149 poniżej.
djvg

Odpowiedzi:

21

Zobacz https://djangosnippets.org/snippets/10539/

class ReadOnlyAdminMixin(object):
    """Disables all editing capabilities."""
    change_form_template = "admin/view.html"

    def __init__(self, *args, **kwargs):
        super(ReadOnlyAdminMixin, self).__init__(*args, **kwargs)
        self.readonly_fields = self.model._meta.get_all_field_names()

    def get_actions(self, request):
        actions = super(ReadOnlyAdminMixin, self).get_actions(request)
        del_action = "delete_selected"
        if del_action in actions:
            del actions[del_action]
        return actions

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    def save_model(self, request, obj, form, change):
        pass

    def delete_model(self, request, obj):
        pass

    def save_related(self, request, form, formsets, change):
        pass

szablony / admin / view.html

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <div class="submit-row">
    <a href="../">{% blocktrans %}Back to list{% endblocktrans %}</a>
  </div>
{% endblock %}

szablony / admin / view.html (dla Grappelli)

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <footer class="grp-module grp-submit-row grp-fixed-footer">
    <header style="display:none"><h1>{% trans "submit options"|capfirst context "heading" %}</h1></header>
    <ul>
       <li><a href="../" class="grp-button grp-default">{% blocktrans %}Back to list{% endblocktrans %}</a></li>
    </ul>
  </footer>
{% endblock %}
Pascal Polleunus
źródło
Wydaje się uzasadnione. Jednak minęło tak dużo czasu, odkąd używałem Django, więc może zaczekam, aż zobaczę, co powiedzą inni komentatorzy.
Steve Bennett
Czy to jest mixin dla Model, czy dla ModelAdmin?
OrangeDog,
To dla ModelAdmin.
Pascal Polleunus
W przypadku Django 1.8 i nowszych, get_all_field_names jest przestarzałe. Zgodny wstecz sposób, aby je zdobyć . Krótki sposób na ich zdobycie .
fzzylogic
Możesz użyć has_add_permission
rluts
70

Administrator służy do edycji, a nie tylko do przeglądania (nie znajdziesz uprawnienia do przeglądania). Aby osiągnąć to, co chcesz, musisz zabronić dodawania, usuwania i ustawiać wszystkie pola jako tylko do odczytu:

class MyAdmin(ModelAdmin):

    def has_add_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

(jeśli zabronisz zmiany, nie zobaczysz nawet obiektów)

W przypadku niektórych nieprzetestowanych kodów, które próbują zautomatyzować ustawianie wszystkich pól tylko do odczytu, moja odpowiedź na cały model jest tylko do odczytu

EDYCJA: również nieprzetestowane, ale właśnie rzuciłem okiem na mój LogEntryAdmin i ma

readonly_fields = MyModel._meta.get_all_field_names()

Nie wiem, czy to zadziała we wszystkich przypadkach.

EDYCJA: QuerySet.delete () może nadal masowo usuwać obiekty. Aby obejść ten problem, zapewnij własnego menedżera "obiektów" i odpowiednią podklasę QuerySet, która nie usuwa - zobacz Przesłanianie QuerySet.delete () w Django

Danny W. Adair
źródło
2
PS: i tak, tak jak w drugiej odpowiedzi, najlepszym sposobem jest prawdopodobnie zdefiniowanie tych trzech rzeczy w klasie ReadOnlyAdmin, a następnie podklasa z tego, gdziekolwiek potrzebujesz tego zachowania. Mogła dostać nawet fantazji i pozwalają na definicję grupy / uprawnienia, które dopuszczone do edycji, a następnie zwróci true odpowiednio (i wykorzystanie get_readonly_fields (), która ma dostęp do wniosku, a zatem bieżącego użytkownika).
Danny W. Adair
Prawie idealnie. czy mógłbym łapczywie zapytać, czy istnieje sposób, aby wiersze nie prowadziły do ​​strony edycji? (znowu nie ma potrzeby powiększania żadnego wiersza i nie ma potrzeby niczego edytować)
Steve Bennett
1
Jeśli ustawisz list_display_links swojego ModelAdmin na coś, co ocenia się jako False (jak pusta lista / krotka), ModelAdmin .__ init __ () ustawia list_display_links na wszystkie kolumny (z wyjątkiem pola wyboru akcji) - zobacz options.py. Myślę, że ma to na celu zapewnienie, że istnieją linki. Więc przesłoniłbym __init __ () w ReadOnlyAdmin, wywołałbym nadrzędny, a następnie ustawił list_display_links na pustą listę lub krotkę. Biorąc pod uwagę, że teraz nie będziesz mieć linków do formularzy zmiany tylko do odczytu, prawdopodobnie najlepiej będzie utworzyć dla tego atrybut parametru / klasy - nie sądziłbym, że jest to ogólnie pożądane zachowanie. Hth
Danny W. Adair
Jeśli chodzi o ustawienie readonly_fields z modelu, to prawdopodobnie nie zadziała, jeśli zastąpisz formularz i dodasz inne pola ... oparcie go na rzeczywistych polach formularza jest prawdopodobnie lepsze.
Danny W. Adair
To nie zadziałało: def __init __ (self, * args): super (RegistrationStatusAdmin, self) .__ init __ (* args) self.display_links = []
Steve Bennett
50

Oto dwie klasy, których używam do tworzenia modelu i / lub jego wbudowane tylko do odczytu.

Dla administratora modelu:

from django.contrib import admin

class ReadOnlyAdmin(admin.ModelAdmin):
    readonly_fields = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in obj._meta.fields] + \
               [field.name for field in obj._meta.many_to_many]


    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

class MyModelAdmin(ReadOnlyAdmin):
    pass

W przypadku inline:

class ReadOnlyTabularInline(admin.TabularInline):
    extra = 0
    can_delete = False
    editable_fields = []
    readonly_fields = []
    exclude = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in self.model._meta.fields
                if field.name not in self.editable_fields and
                   field.name not in self.exclude]

    def has_add_permission(self, request):
        return False


class MyInline(ReadOnlyTabularInline):
    pass
darklow
źródło
Jak zastosować obie klasy do jednej podklasy. Np. Jeśli w klasie mam zwykłe pola i inline? Czy mogę przedłużyć oba?
Timo
@timo używa tych klas jako mixinów
MartinM
1
has_add_permissionw ReadOnlyAdminprzyjmuje tylko żądanie jako parametr
MartinM
funkcja has_change_permission () również musi zostać zastąpiona. def has_change_permission (self, request, obj = None):
david euler
13

Jeśli chcesz, aby użytkownik uświadomił sobie, że nie może go edytować, w pierwszym rozwiązaniu brakuje 2 elementów. Usunąłeś akcję usuwania!

class MyAdmin(ModelAdmin)
    def has_add_permission(self, request, obj=None):
        return False
    def has_delete_permission(self, request, obj=None):
        return False

    def get_actions(self, request):
        actions = super(MyAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

Po drugie: rozwiązanie tylko do odczytu działa dobrze na zwykłych modelach. Ale to NIE działa, jeśli masz odziedziczony model z obcymi kluczami. Niestety nie znam jeszcze rozwiązania tego problemu. Dobra próba to:

Cały model tylko do odczytu

Ale dla mnie to też nie działa.

I ostatnia uwaga, jeśli chcesz pomyśleć o szerokim rozwiązaniu, musisz wymusić, że każdy wiersz w wierszu również musi być tylko do odczytu.

Josir
źródło
11

Właściwie możesz wypróbować to proste rozwiązanie:

class ReadOnlyModelAdmin(admin.ModelAdmin):
    actions = None
    list_display_links = None
    # more stuff here

    def has_add_permission(self, request):
        return False
  • actions = None: unika wyświetlania menu z opcją „Usuń wybrane ...”
  • list_display_links = None: unika klikania kolumn w celu edycji tego obiektu
  • has_add_permission() zwracanie wartości False pozwala uniknąć tworzenia nowych obiektów dla tego modelu
Iván Zugnoni
źródło
1
Zabrania to otwierania jakiejkolwiek instancji do przeglądania pól, jednak jeśli wystarczy tylko listowanie, to działa.
Sebastián Vansteenkiste
8

To zostało dodane do Django 2.1, które zostało wydane 18.08.18!

ModelAdmin.has_view_permission()jest taki sam, jak istniejące has_delete_permission, has_change_permission i has_add_permission. Możesz o tym przeczytać w dokumentach tutaj

Z informacji o wydaniu:

Pozwala to zapewnić użytkownikom dostęp tylko do odczytu do modeli w panelu administracyjnym. ModelAdmin.has_view_permission () jest nowy. Implementacja jest kompatybilna wstecz, ponieważ nie ma potrzeby przypisywania uprawnienia do „przeglądania”, aby umożliwić użytkownikom, którzy mają uprawnienia do „zmiany”, edytowanie obiektów.

grrrrrr
źródło
Jednak superużytkownik nadal będzie mógł modyfikować obiekty w interfejsie administratora, prawda?
Flimm
To prawda, chyba że zastąpisz jedną z tych metod, aby zmienić zachowanie i uniemożliwić dostęp superużytkownikom.
grrrrrr
6

Jeśli zaakceptowana odpowiedź nie działa dla Ciebie, spróbuj tego:

def get_readonly_fields(self, request, obj=None):
    readonly_fields = []
    for field in self.model._meta.fields:
        readonly_fields.append(field.name)

    return readonly_fields
Wouter
źródło
5

Kompilowanie doskonałych odpowiedzi @darklow i @josir oraz dodanie nieco więcej w celu usunięcia przycisków „Zapisz” i „Zapisz i kontynuuj” prowadzi do (w składni Pythona 3):

class ReadOnlyAdmin(admin.ModelAdmin):
    """Provides a read-only view of a model in Django admin."""
    readonly_fields = []

    def change_view(self, request, object_id, extra_context=None):
        """ customize add/edit form to remove save / save and continue """
        extra_context = extra_context or {}
        extra_context['show_save_and_continue'] = False
        extra_context['show_save'] = False
        return super().change_view(request, object_id, extra_context=extra_context)

    def get_actions(self, request):
        actions = super().get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
           [field.name for field in obj._meta.fields] + \
           [field.name for field in obj._meta.many_to_many]

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

a potem używasz like

class MyModelAdmin(ReadOnlyAdmin):
    pass

Próbowałem tego tylko z Django 1.11 / Python 3.

Mark Chackerian
źródło
Minęło dużo czasu, odkąd używałem Django. Czy ktoś może za to ręczyć?
Steve Bennett
@SteveBennett ㄹ istnieje wiele odmian wymagań, które spełnia ten adres ... ta odpowiedź nie jest wodoszczelna ... zasugeruj wyjaśnienie tutaj: stackoverflow.com/a/36019597/2586761 i odpowiedź, którą skomentowałeś na stackoverflow.com / a / 33543817/2586761 jako bardziej kompletna niż zaakceptowana odpowiedź
ptim
3

Zaakceptowana odpowiedź powinna działać, ale zachowa to również kolejność wyświetlania pól tylko do odczytu. Dzięki temu rozwiązaniu nie musisz też na stałe kodować modelu.

class ReadonlyAdmin(admin.ModelAdmin):
   def __init__(self, model, admin_site):
      super(ReadonlyAdmin, self).__init__(model, admin_site)
      self.readonly_fields = [field.name for field in filter(lambda f: not f.auto_created, model._meta.fields)]

   def has_delete_permission(self, request, obj=None):
       return False
   def has_add_permission(self, request, obj=None):
       return False
lastoneisbearfood
źródło
3

Z Django 2.2 robię to tak:

@admin.register(MyModel)
class MyAdmin(admin.ModelAdmin):
    readonly_fields = ('all', 'the', 'necessary', 'fields')
    actions = None # Removes the default delete action in list view

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
Eerik Sven Puudist
źródło
z django 2.2 linie readonly_fieldsi actionsnie są konieczne
cheng10
3

z django 2.2 administrator tylko do odczytu może być tak prosty, jak:

class ReadOnlyAdminMixin():
    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


class LogEntryAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
    list_display = ('id', 'user', 'action_flag', 'content_type', 'object_repr')
cheng10
źródło
1

Napotkałem ten sam wymóg, gdy musiałem uczynić wszystkie pola tylko do odczytu dla niektórych użytkowników w administratorze django, co skończyło się wykorzystaniem modułu django "django-admin-uprawnienia-widok" bez zmiany własnego kodu. Jeśli potrzebujesz dokładniejszej kontroli, aby wyraźnie określić, które pola, musisz rozszerzyć moduł. Możesz sprawdzić, jak działa wtyczka tutaj

Timothy Mugayi
źródło
0

tylko do odczytu => uprawnienia do przeglądania

  1. pipenv install django-admin-view-permission
  2. dodaj 'admin_view_permission' do INSTALLED_APPS w pliku settings.py. w ten sposób: `INSTALLED_APPS = ['admin_view_permission',
  3. python manage.py migrate
  4. python manage.py runerver 6666

ok. baw się dobrze za zgodą „widoki”

Xianhong Xu
źródło
0

Napisałem ogólną klasę do obsługi widoku ReadOnly w zależności od uprawnień użytkownika, w tym inline;)

W models.py:

class User(AbstractUser):
    ...
    def is_readonly(self):
        if self.is_superuser:
            return False
        # make readonly all users not in "admins" group
        adminGroup = Group.objects.filter(name="admins")
        if adminGroup in self.groups.all():
            return False
        return True

W admin.py:

# read-only user filter class for ModelAdmin
class ReadOnlyAdmin(admin.ModelAdmin):
    def __init__(self, *args, **kwargs):
        # keep initial readonly_fields defined in subclass
        self._init_readonly_fields = self.readonly_fields
        # keep also inline readonly_fields
        for inline in self.inlines:
            inline._init_readonly_fields = inline.readonly_fields
        super().__init__(*args,**kwargs)
    # customize change_view to disable edition to readonly_users
    def change_view( self, request, object_id, form_url='', extra_context=None ):
        context = extra_context or {}
        # find whether it is readonly or not 
        if request.user.is_readonly():
            # put all fields in readonly_field list
            self.readonly_fields = [ field.name for field in self.model._meta.get_fields() if not field.auto_created ]
            # readonly mode fer all inlines
            for inline in self.inlines:
                inline.readonly_fields = [field.name for field in inline.model._meta.get_fields() if not field.auto_created]
            # remove edition buttons
            self.save_on_top = False
            context['show_save'] = False
            context['show_save_and_continue'] = False
        else:
            # if not readonly user, reset initial readonly_fields
            self.readonly_fields = self._init_readonly_fields
            # same for inlines
            for inline in self.inlines:
                inline.readonly_fields = self._init_readonly_fields
        return super().change_view(
                    request, object_id, form_url, context )
    def save_model(self, request, obj, form, change):
        # disable saving model for readonly users
        # just in case we have a malicious user...
        if request.user.is_readonly():
            # si és usuari readonly no guardem canvis
            return False
        # if not readonly user, save model
        return super().save_model( request, obj, form, change )

Następnie możemy normalnie dziedziczyć nasze klasy w admin.py:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )
Enric Mieza
źródło