Налаштуйте / видаліть пусту опцію Django


77

Я використовую Django 1.0.2. Я написав ModelForm, підкріплений Model. Ця модель має клавішу ForeignKey, де blank = False. Коли Django генерує HTML для цієї форми, він створює поле вибору з одним параметром для кожного рядка в таблиці, на який посилається ForeignKey. Він також створює опцію у верхній частині списку, яка не має значення і відображається у вигляді серії тире:

<option value="">---------</option>

Я хотів би знати:

  1. Який найчистіший спосіб видалити цю автоматично згенеровану опцію з поля вибору?
  2. Який найчистіший спосіб налаштувати його так, щоб він відображався як:

    <option value="">Select Item</option>
    

У пошуках рішення я натрапив на білет Django 4653, який створив у мене враження, що у інших було таке саме запитання і що поведінка Django за замовчуванням могла бути змінена. Цій квитку більше року, тому я сподівався, що може бути чистіший спосіб виконати ці справи.

Дякуємо за будь-яку допомогу,

Джефф

Редагувати: Я налаштував поле ForeignKey як таке:

verb = models.ForeignKey(Verb, blank=False, default=get_default_verb)

Це встановлює значення за замовчуванням, так що це більше не параметр пусті / тире, але, на жаль, це, здається, не вирішує жодного з моїх питань. Тобто параметр порожні / тире все ще відображається у списку.


Виявляється, це вважається помилкою в Django; у цьому випадку слід автоматично видалити порожній параметр: code.djangoproject.com/ticket/10792
Карл Мейєр,

Відповіді:


90

Не тестував цього, але на основі прочитання коду Django тут і тут я вважаю, що це повинно спрацювати:

class ThingForm(models.ModelForm):
  class Meta:
    model = Thing

  def __init__(self, *args, **kwargs):
    super(ThingForm, self).__init__(*args, **kwargs)
    self.fields['verb'].empty_label = None

РЕДАГУВАТИ : Це задокументовано , хоча вам не обов'язково знати, як шукати ModelChoiceField, якщо ви працюєте з автоматично згенерованою ModelForm.

РЕДАГУВАТИ : Як зазначає jlpp у своїй відповіді, це не завершено - вам доведеться повторно призначати вибір віджетам після зміни атрибута empty_label. Оскільки це трохи невдало, інший варіант, який може бути простішим для розуміння, просто перекриває весь ModelChoiceField:

class ThingForm(models.ModelForm):
  verb = ModelChoiceField(Verb.objects.all(), empty_label=None)

  class Meta:
    model = Thing

Дякую за допомогу Карлу. Ваш приклад майже спрацював, але останнім кроком мені довелося оновити список віджетів. Дивіться мою відповідь. І нічого собі; швидкий поворот звіту про помилку!
jlpp

Бічна панель: чи знаєте ви, як ваш останній підхід містився б у використанні полів Meta:, щоб вибрати поля моделі, які слід включити у форму?
jlpp

Я не впевнений, що сталося б, якщо ви визначите поле форми вручну, але не включите його до списку мета поля моделей. Я впевнений, що він з’явиться, але чи буде його збережено на моделі? Я думаю, так - спробуйте і подивіться.
Carl Meyer,

Я думаю, що другий спосіб зробити це за допомогою визначення ModelChoiceField є більш зрозумілим. Легше помітити, якщо дивитись на когось ще коду :)
радтек

Перше рішення добре працює для мене без необхідності перепризначати вибір.
ZAD-Man

38

з документів

Порожній вибір не буде включений, якщо в полі моделі є blank = False та явне значення за замовчуванням (замість цього спочатку буде вибрано значення за замовчуванням).

так що встановіть за замовчуванням, і все в порядку


Див. Редагувати питання. Спасибі за вашу допомогу.
jlpp,

3
Робити це для поля ForeignKey може бути небезпечно. Це може спрацювати, це CharField з вибором. Проблема полягає у тому, що ForeignKey не має чітко встановлених значень за замовчуванням.
amjoconn

Так, це було саме те, що я шукав ... для полів, що не належать до ForeignKey
byoungb

23

З відповіддю Карла як керівництвом та після того, як пару годин навколо джерела Django я думаю, це повне рішення:

  1. Щоб видалити порожній параметр (розширюючи приклад Карла):

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = None
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    
  2. Налаштувати порожню мітку параметрів по суті однаково:

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = "Select a Verb"
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    

Я думаю, що цей підхід застосовується до всіх сценаріїв, коли ModelChoiceFields відображаються як HTML, але я не впевнений. Я виявив, що коли ці поля ініціалізовані, їх вибір передається віджету Select (див. Django.forms.fields.ChoiceField._set_choices). Встановлення мітки empty_label після ініціалізації не оновлює список вибору віджета. Я недостатньо знайомий з Django, щоб знати, чи слід це вважати помилкою.


Можливо, не помилка; Я думаю, що є причина, чому аргумент empty_label задокументований, але не повторне встановлення атрибута. Оскільки це свого роду хакі, кращим варіантом може бути просто перевизначення всього поля форми; див. мою відредаговану відповідь.
Карл Мейер,

2
Це дивно, але я щойно перевірив це у своїй програмі, і мені не потрібно було скидати поля, щоб видалити порожню мітку ...
Паоло Бергантіно,

20

Ви можете використовувати це на своїй моделі:

class MyModel(models.Model):
    name = CharField('fieldname', max_length=10, default=None)

за замовчуванням = None - це відповідь: D

ПРИМІТКА: Я спробував це на Django 1.7


2
Відмінно. Як зазначено за цим посиланням , поле моделі з blank=Falseбудь-яким defaultнабором значень не відображатиме порожній варіант вибору.
emyller

1
Приємне просте рішення! Всі інші межують з божевільним з точки зору складності.
Роберт Москаль,

Фантастичний. Набагато краще, ніж інші ~ 9 видів робочих відповідей на ~ 3 версії цього питання.
foobarbecue

8

Що стосується django 1.4, то все, що вам потрібно, це встановити значення "за замовчуванням" і "blank = False" у полі вибору

class MyModel(models.Model):
    CHOICES = (
        (0, 'A'), 
        (1, 'B'),
    )
    choice_field = models.IntegerField(choices=CHOICES, blank=False, default=0)


5

Ви можете зробити це в адміністраторі:

formfield_overrides = {
    models.ForeignKey: {'empty_label': None},
}

4

self.fields['xxx'].empty_value = Noneне буде працювати, якщо ви вводите тип поля, TypedChoiceFieldякий не має empty_labelвластивості.

Що ми повинні зробити, це видалити перший вибір:

1. Якщо ви хочете побудувати BaseFormавтоматичне виявленняTypedChoiceField

class BaseForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(BaseForm, self).__init__(*args, **kwargs)

        for field_name in self.fields:
            field = self.fields.get(field_name)
            if field and isinstance(field , forms.TypedChoiceField):
                field.choices = field.choices[1:]
            # code to process other Field
            # ....

class AddClientForm(BaseForm):
     pass

2. лише кілька форм, ви можете використовувати:

class AddClientForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(AddClientForm, self).__init__(*args, **kwargs)
        self.fields['xxx'].choices = self.fields['xxx'].choices[1:]

Це справжня проблема, дякую за пояснення. Але, здається, "за" не є обов'язковим, ми можемо просто зробити, self.fields['xxx'].choices = self.fields['xxx'].choices[1:]якщо хочемо прибрати порожній вибір. Але якщо ви хочете замінити заголовок, вам потрібно "відтворити" перший вибір як кортеж з першим порожнім елементом та заголовком у другому, наприкладself.fields['xxx'].choices = [('', 'Nothing')] + self.fields['xxx'].choices[1:]
Іван Борщов

@ user3479125 Я роблю, BaseFormщоб налаштувати всю форму, тому використовую цикл for.
Міфріл

4

Для ForeignKeyполя, встановивши defaultзначення ''на моделі, буде видалено порожній параметр.

verb = models.ForeignKey(Verb, on_delete=models.CASCADE, default='')

Для інших полів , як CharFieldви могли б встановити defaultв None, але це не працює для ForeignKeyполів в Django 1.11.


2

Я був возитися з цим сьогодні і просто придумав боягуз хак витончене рішення:

# Cowardly handle ModelChoiceField empty label
# we all hate that '-----' thing
class ModelChoiceField_init_hack(object):
    @property
    def empty_label(self):
        return self._empty_label

    @empty_label.setter
    def empty_label(self, value):
        self._empty_label = value
        if value and value.startswith('-'):
            self._empty_label = 'Select an option'
ModelChoiceField.__bases__ += (ModelChoiceField_init_hack,)

Тепер ви можете налаштувати ModelChoiceFieldпорожню мітку за замовчуванням на що завгодно. :-)

PS: Не потрібно голосувати проти, нешкідливі патчі мавп завжди зручні.


2

Для останньої версії django перша відповідь повинна бути такою

class ThingForm(models.ModelForm):
class Meta:
 model = Thing

  def __init__(self, *args, **kwargs):
    self.base_fields['cargo'].empty_label = None
    super(ThingForm, self).__init__(*args, **kwargs)`

1

Я знаходжу РІШЕННЯ !!

Але не для ForeignKey :-)

Можливо, я можу вам допомогти. Я заглянув у вихідний код Django і виявив, що в django.forms.extras.widgets.SelecteDateWidget () - це властивість з назвою none_value, що дорівнює (0, '-----'), тому я зробив у своєму коді це

class StudentForm(ModelForm):
    class Meta:
        this_year = int(datetime.datetime.today().strftime('%Y')) 
        birth_years = []
        years = []

        for year in range(this_year - 2, this_year + 3 ):
            years.append(year)
        for year in range(this_year - 60, this_year+2):
            birth_years.append(year)

        model = Student
        exclude = ['user', 'fullname']
        date_widget = SelectDateWidget(years=years)

        date_widget.__setattr__('none_value', (0, 'THERE WAS THAT "-----" NO THERES THIS:-)'))
        widgets = {
            'beginning': date_widget,
            'birth': SelectDateWidget(years=birth_years),
        }

1
Не точно стосується питання, але привело мене до вирішення проблеми, яку я мав. У моєму випадку я просто намагався змінити відображення за замовчуванням на нульове значення, яке не прив’язане до моделі. Це значення за замовчуванням "--------", яке я хотів змінити. Якщо це так, тоді просто зробіть:self.fields[field_name].widget.choices[0] = ('', SOME_FIRST_CHOICE_DISPLAY_VALUE)
Трой Гросфілд

1

Починаючи з Django 1.7, ви можете налаштувати ярлик для порожнього значення, додавши значення до списку вибору у визначенні поля вашої моделі. З документації щодо налаштування вибору поля :

Якщо в полі не встановлено порожнє значення = False, а також значення за замовчуванням, тоді мітка, що містить "---------", буде відображена у полі вибору. Щоб замінити цю поведінку, додайте кортеж до варіантів, що містять None; наприклад (None, 'Your String For Display'). Крім того, ви можете використовувати порожній рядок замість None, якщо це має сенс - наприклад, на CharField.

Я перевірив документацію для різних версій Django і виявив, що це було додано до Django 1.7 .


0

Тут є багато чудових відповідей, але я все ще не цілком задоволений реалізаціями. Мені також трохи неприємно, що віджети з різних джерел (зовнішні ключі, вибір) дають різну поведінку.

У мене є дизайн, з яким я працюю, де у вибраних полях завжди є порожній параметр, і якщо вони потрібні, вони матимуть зірочку поруч і форма просто не перевірятиметься, якщо вони залишаться порожніми. Тим не менш, я можу належним чином перевизначити поле empty_label для полів, які не є TypedChoiceFields.

Ось як повинен виглядати результат . Перший результат завжди ім'я поля - в моєму випадку label.

виберіть віджет

Ось що я в підсумку зробив. Нижче наведено перевизначений __init__метод моєї форми:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for _, field in self.fields.items():
        if hasattr(field, 'empty_label'):
            field.empty_label = field.label
        if isinstance(field, forms.TypedChoiceField):
            field.choices = [('', field.label)] + [choice for choice in field.choices if choice[0]]

0

Це ускладнюється, коли вибір - зовнішні ключі, і якщо ви хочете відфільтрувати вибір на основі деяких критеріїв . У такому випадку, якщо ви встановите, empty_labelа потім повторно призначите варіанти (ви також можете застосувати фільтрацію тут), порожня мітка буде порожньою:

class ThingForm(models.ModelForm):

    class Meta:
    model = Thing

    def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = None
        self.fields['verb'].queryset=Verb.objects.all()

загалом, перший рядок initможна застосувати до всіх полів у формі з циклом або вбудованим циклом:

def __init__(self,user, *args, **kwargs):
    super(NewTicket, self).__init__(*args, **kwargs)
    for f in self.fields:
       self.fields[f].empty_label = None # or "Please Select" etc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.