django admin - dodaj niestandardowe pola formularzy, które nie są częścią modelu

108

Mam model zarejestrowany w serwisie administratora. Jedno z jego pól to długie wyrażenie łańcuchowe. Chciałbym dodać niestandardowe pola formularzy do strony dodawania / aktualizowania tego modelu w administratorze, który na podstawie wartości tych pól zbuduję długie wyrażenie i zapiszę je w odpowiednim polu modelu.

Jak mogę to zrobić?

AKTUALIZACJA: Zasadniczo to, co robię, to budowanie wyrażenia matematycznego lub tekstowego z symboli, użytkownik wybiera symbole (są to niestandardowe pola, które nie są częścią modelu), a kiedy kliknie przycisk Zapisz, tworzę reprezentację wyrażenia łańcuchowego z listę symboli i zapisz ją w DB. Nie chcę, aby symbole były częścią modelu i DB, tylko ostatecznym wyrażeniem.

michalv82
źródło

Odpowiedzi:

156

Albo w twoim admin.py albo w oddzielnym formularzs.py możesz dodać klasę ModelForm, a następnie zadeklarować dodatkowe pola wewnątrz niej, jak zwykle. Podałem również przykład, jak możesz użyć tych wartości w form.save ():

from django import forms
from yourapp.models import YourModel


class YourModelForm(forms.ModelForm):

    extra_field = forms.CharField()

    def save(self, commit=True):
        extra_field = self.cleaned_data.get('extra_field', None)
        # ...do something with extra_field here...
        return super(YourModelForm, self).save(commit=commit)

    class Meta:
        model = YourModel

Aby dodatkowe pola pojawiały się w panelu administracyjnym, wystarczy:

  1. edytuj swój admin.py i ustaw właściwość formularza tak, aby odnosiła się do formularza utworzonego powyżej
  2. dołącz nowe pola do deklaracji pól lub zestawów pól

Lubię to:

class YourModelAdmin(admin.ModelAdmin):

    form = YourModelForm

    fieldsets = (
        (None, {
            'fields': ('name', 'description', 'extra_field',),
        }),
    )

UPDATE: W django 1.8 musisz dodać fields = '__all__'do metaklasy YourModelForm.

Wisznu
źródło
2
Tak, naprawdę chcę poznać powód
Wisznu
14
Powinieneś dodać fields = '__all__'do swojej Metaklasy, w przeciwnym razie django narzeka:Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is deprecated
IsaacKleiner
1
@sthzg, ponieważ nie jest poprawne. Daje mi błąd:YourModelAdmin.list_display[0], 'extra_field' is not a callable or an attribute of 'YourModelAdmin' or found in the model 'YourModel'.
Cerin
7
Jeśli z jakiegoś powodu otrzymasz AttributeError: Unable to lookup "extra_field"...znak, spróbuj dodać a labeldo extra_fielddefinicji. Wydaje się, że Django stara się „odgadnąć” etykietę na to, by patrząc na Modeli ModelAdmindla takiej definicji atrybutu.
alxs
1
Działało to pięknie, jeśli extra_field jest CharField (). Jeśli jest to jednak hiddenField [w Django 1.11], generowany jest błąd Unknown field(s) (extra_field) specified for YourModel. Check fields/fieldsets/exclude attributes of class YourModelAdmin.. Sposób obejścia tego problemu toextra_field = forms.CharField(widget=forms.HiddenInput())
kakoma
36

Można to zrobić w adminie, ale nie ma do tego prostego sposobu. Chciałbym również poradzić, aby zachować większość logiki biznesowej w modelach, abyś nie był zależny od Django Admin.

Może byłoby łatwiej (a może nawet lepiej), gdybyś miał dwa oddzielne pola w swoim modelu. Następnie dodaj do modelu metodę, która je łączy.

Na przykład:

class MyModel(models.model):

    field1 = models.CharField(max_length=10)
    field2 = models.CharField(max_length=10)

    def combined_fields(self):
        return '{} {}'.format(self.field1, self.field2)

Następnie w panelu administracyjnym możesz dodać combined_fields()pole tylko do odczytu:

class MyModelAdmin(models.ModelAdmin):

    list_display = ('field1', 'field2', 'combined_fields')
    readonly_fields = ('combined_fields',)

    def combined_fields(self, obj):
        return obj.combined_fields()

Jeśli chcesz przechowywać combined_fieldsw bazie danych, możesz również zapisać go podczas zapisywania modelu:

def save(self, *args, **kwargs):
    self.field3 = self.combined_fields()
    super(MyModel, self).save(*args, **kwargs)
gitaarik
źródło
4
Dzięki za odpowiedź, ale nie tego szukam. Nie chcę, aby pola niestandardowe były zapisywane w bazie danych, tylko obliczony ciąg. Zasadniczo to, co robię, to budowanie wyrażenia matematycznego lub tekstowego z symboli, użytkownik wybiera symbole (są to niestandardowe pola, które nie są częścią modelu), a kiedy kliknie przycisk Zapisz, tworzę reprezentację wyrażenia łańcuchowego z listy symboli i przechowuj je w DB.
michalv82
@ michalv82 Możesz również zapisać go do bazy danych save()metodą modelu , sprawdź aktualizacje mojej odpowiedzi.
gitaarik
1
jeszcze raz dziękuję, ale problem polega na tym, że nie chcę przechowywać pól, które łączą ostatnie pole (czyli symbole), chcę tylko zapisać końcowy ciąg
michalv82
Czy zapisanie 2 pól jest problemem? Może się przyda, jeśli chcesz wiedzieć, w jaki sposób wygenerowano połączone pole.
gitaarik
6
dzięki jeszcze raz ale to nie 2 pola, pewnie będzie więcej. Znowu NIE CHCĘ przechowywać ich w DB, więc to rozwiązanie nie działa dla mnie.
michalv82
6

Django 2.1.1 Główna odpowiedź pozwoliła mi odpowiedzieć na moje pytanie. Nie pomogło mi to zapisać wyniku w polu w moim rzeczywistym modelu. W moim przypadku chciałem mieć pole tekstowe, do którego użytkownik mógłby wprowadzić dane, a następnie, gdy nastąpi zapis, dane zostaną przetworzone, a wynik zostanie umieszczony w polu w modelu i zapisany. Chociaż pierwotna odpowiedź pokazała, jak uzyskać wartość z dodatkowego pola, nie pokazała, jak zapisać ją z powrotem do modelu, przynajmniej w Django 2.1.1

Pobiera wartość z niezwiązanego pola niestandardowego, przetwarza i zapisuje ją w moim prawdziwym polu opisu:

class WidgetForm(forms.ModelForm):
    extra_field = forms.CharField(required=False)

    def processData(self, input):
        # example of error handling
        if False:
            raise forms.ValidationError('Processing failed!')

        return input + " has been processed"

    def save(self, commit=True):
        extra_field = self.cleaned_data.get('extra_field', None)

        # self.description = "my result" note that this does not work

        # Get the form instance so I can write to its fields
        instance = super(WidgetForm, self).save(commit=commit)

        # this writes the processed data to the description field
        instance.description = self.processData(extra_field)

        if commit:
            instance.save()

        return instance

    class Meta:
        model = Widget
        fields = "__all__"
Indyjski koktajl jogurtowy z mango
źródło
4

zawsze możesz utworzyć nowy szablon administratora i robić to, czego potrzebujesz w swoim admin_view (nadpisz adres administratora dodając adres URL do swojego admin_view):

 url(r'^admin/mymodel/mymodel/add/$' , 'admin_views.add_my_special_model')
Eyal Ch
źródło
3

Jeśli absolutnie chcesz przechowywać tylko połączone pole w modelu, a nie dwa oddzielne pola, możesz zrobić coś takiego:

Nigdy czegoś takiego nie robiłem, więc nie jestem do końca pewien, jak to się skończy.

gitaarik
źródło