Get fields error

Here's my saveAction code (where the form passes the data to) public function saveAction() { $user = OBUser(); $form = $this->createForm(new OBUserType(), $user); if ($this->re...

Based on @Jay Seth’s answer, I made a version of FormErrors class especially for Ajax Forms:

// src/AppBundle/Form/FormErrors.php
namespace AppBundleForm;

class FormErrors
{

    /**
     * @param SymfonyComponentFormForm $form
     *
     * @return array $errors
     */
    public function getArray(SymfonyComponentFormForm $form)
    {
        return $this->getErrors($form, $form->getName());
    }

    /**
     * @param SymfonyComponentFormForm $baseForm
     * @param SymfonyComponentFormForm $baseFormName
     *
     * @return array $errors
     */
    private function getErrors($baseForm, $baseFormName) {
        $errors = array();
        if ($baseForm instanceof SymfonyComponentFormForm) {
            foreach($baseForm->getErrors() as $error) {
                $errors[] = array(
                    "mess"      => $error->getMessage(),
                    "key"       => $baseFormName
                );
            }

            foreach ($baseForm->all() as $key => $child) {
                if(($child instanceof SymfonyComponentFormForm)) {
                    $cErrors = $this->getErrors($child, $baseFormName . "_" . $child->getName());
                    $errors = array_merge($errors, $cErrors);
                }
            }
        }
        return $errors;
    }
}

Usage (e.g. in your action):

$errors = $this->get('form_errors')->getArray($form);

Symfony version: 2.8.4

Example JSON response:

{
    "success": false,
    "errors": [{
        "mess": "error_message",
        "key": "RegistrationForm_user_firstname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_lastname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_email"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_zipCode"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_password_password"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_marketing"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_marketing"
    }]
}

The error object contains the «key» field, which is the id of the input DOM element, so you can easily populate error messages.

If you have child forms inside the parent, don’t forget to add the cascade_validation option inside the parent form’s setDefaults.

Первая часть
Вторая часть

7 Проверка и сообщения об ошибках

Большинство наших форм должны показывать сообщения проверки, чтобы информировать пользователя об ошибках, которые он сделал.

Thymeleaf предлагает несколько инструментов для этого: несколько функций в объекте #fields, атрибуты th:errors и th:errorclass.

7.1 Field errors

Давайте посмотрим, как мы можем установить конкретный класс CSS для поля, если оно содержит ошибку:

<input type="text" th:field="*{datePlanted}" 
                   th:class="${#fields.hasErrors('datePlanted')}? fieldError" />

Как видите, функция #fields.hasErrors(…) получает выражение поля в качестве параметра (datePlanted) и возвращает логическое значение, указывающее, существуют ли какие-либо ошибки проверки для этого поля.

Мы также можем получить все ошибки для этого поля и повторить их:

<ul>
  <li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
</ul>

Вместо итерации мы могли бы также использовать th:errors, специализированный атрибут, который создает список со всеми ошибками для указанного селектора, разделенными <br />:

<input type="text" th:field="*{datePlanted}" />
<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>

Упрощение стилей CSS на основе ошибок: th:errorclass

Пример, который мы видели выше, установка CSS-класса для input формы, если в этом поле есть ошибки, настолько распространена, что Thymeleaf предлагает специальный атрибут для точного выполнения: th:errorclass.

Примененный к тегу поля формы (input, select, textarea…), он будет считывать имя поля, которое нужно проверить, из любых существующих атрибутов name или th:field в том же теге, а затем добавлять указанный класс CSS к тегу, если такое поле имеет какие-либо связанные ошибки:

<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />

Если в datePlanted есть ошибки, это будет выглядеть так:

<input type="text" id="datePlanted" name="datePlanted" value="2013-01-01" class="small fieldError" />

7.2 Все ошибки

А что если мы хотим показать все ошибки в форме? Нам просто нужно запросить методы #fields.hasErrors(…) и #fields.errors(…) с константами ‘*‘ или ‘all‘ (которые эквивалентны):

<ul th:if="${#fields.hasErrors('*')}">
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>

Как и в приведенных выше примерах, мы могли бы получить все ошибки и итерировать по ним …

<ul>
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>

… а также создать разделенный <br /> список:

<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}">Incorrect date</p>

Наконец, обратите внимание, что #fields.hasErrors(‘*’) эквивалентно #fields.hasAnyErrors(), и #fields.errors(‘*’) эквивалентно #fields.allErrors(). Используйте тот синтаксис, который вы предпочитаете:

<div th:if="${#fields.hasAnyErrors()}">
  <p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>

7.3 Глобальные ошибки

В форме Spring существует третий тип ошибок: глобальные ошибки. Это ошибки, которые не связаны с какими-либо конкретными полями в форме, но все еще существуют.
Thymeleaf предлагает константу global для доступа к этим ошибкам:

<ul th:if="${#fields.hasErrors('global')}">
  <li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
</ul>

<p th:if="${#fields.hasErrors('global')}" th:errors="*{global}">Incorrect date</p>

… a также эквивалентные вспомогательные методы #fields.hasGlobalErrors() и #fields.globalErrors():

7.4 Отображение ошибок вне форм

Ошибки проверки формы также могут отображаться вне форм с помощью переменных (${…}) вместо выражений выбора (*{…}) и префикса имени компонента, поддерживающего форму:

<div th:errors="${myForm}">...</div>
<div th:errors="${myForm.date}">...</div>
<div th:errors="${myForm.*}">...</div>

<div th:if="${#fields.hasErrors('${myForm}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.date}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.*}')}">...</div>

<form th:object="${myForm}">
    ...
</form>

7.5 Богатые объекты ошибок

Thymeleaf предлагает возможность получения информации об ошибках формы в виде bean-компонентов (вместо простых строк) с атрибутами fieldName (String), message (String) и global (boolean).

Эти ошибки могут быть получены с помощью служебного метода #fields.detailedErrors():

<ul>
    <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
        <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
        <span th:text="${e.message}">The error message</span>
    </li>
</ul>

8 Это все еще прототип!

Наше приложение готово. Но давайте еще раз посмотрим на .html страницу, которую мы создали …

Одним из самых приятных последствий работы с Thymeleaf является то, что после всех этих функций, которые мы добавили в наш HTML, мы все равно можем использовать этот HTML в качестве прототипа (мы говорим, что это Natural Template). Давайте откроем seedstartermng.html прямо в нашем браузере, не запуская наше приложение:

image

Вот оно! Это не работающее приложение, это не реальные данные … но это совершенно правильный прототип, составленный из прекрасно отображаемого HTML-кода.

9 Служба конверсии (The Conversion Service)

9.1 Конфигурирование

Как объяснялось ранее, Thymeleaf может использовать Службу преобразования, зарегистрированную в контексте приложения. Наш класс конфигурации приложения, расширяя собственный помощник Spring WebMvcConfigurerAdapter, автоматически зарегистрирует такой сервис преобразования, который мы можем настроить, добавив необходимые средства форматирования. Давайте еще раз посмотрим, как это выглядит:

@Override
public void addFormatters(final FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addFormatter(varietyFormatter());
    registry.addFormatter(dateFormatter());
}

@Bean
public VarietyFormatter varietyFormatter() {
    return new VarietyFormatter();
}

@Bean
public DateFormatter dateFormatter() {
    return new DateFormatter();
}

9.2 Синтаксис двойной скобки

Служба преобразования может быть легко применена для преобразования/форматирования любого объекта в строку. Это делается с помощью синтаксиса выражений в двойных скобках:

  • Для переменных выражений: ${{…}}
  • Для выражения выбора: *{{…}}

Так, например, учитывая преобразователь Integer-to-String, который добавляет запятые в качестве разделителя тысяч, это:

<p th:text="${val}">...</p>
<p th:text="${{val}}">...</p>

… должно привести к:

<p>1234567890</p>
<p>1,234,567,890</p>

9.3 Использование в формах

Ранее мы видели, что каждый атрибут th:field всегда будет применять сервис преобразования, так что это:

<input type="text" th:field="*{datePlanted}" />

… фактически эквивалентно:

<input type="text" th:field="*{{datePlanted}}" />

Обратите внимание, что согласно требованию Spring, это единственный сценарий, в котором служба преобразования применяется в выражениях с использованием синтаксиса с одной скобкой.

9.4 #conversions объект преобразования

Служебный объект преобразования #conversions позволяет вручную запускать службу преобразования там, где это необходимо:

<p th:text="${'Val: ' + #conversions.convert(val,'String')}">...</p>

Синтаксис для этого служебного объекта:

  • #conversions.convert(Object, Class): преобразует объект в указанный класс
  • #conversions.convert(Object, String): то же, что и выше, но с указанием целевого класса в виде String (обратите внимание, что пакет java.lang. может быть опущен)

10 Отрисовка фрагментов шаблона Template Fragments (AJAX etc)

Thymeleaf предлагает возможность визуализации только части шаблона в результате его выполнения: фрагмента.

Это может быть полезным инструментом компонентизации. Например, его можно использовать на контроллерах, которые выполняются при вызовах AJAX, которые могут возвращать фрагменты разметки страницы, которая уже загружена в браузер (для обновления выбора, включения/выключения кнопок…).

Фрагментарный рендеринг может быть достигнут с использованием спецификаций фрагментов Thymeleaf: объектов, реализующих интерфейс org.thymeleaf.fragment.IFragmentSpec.

Наиболее распространенной из этих реализаций является org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec, которая позволяет указывать фрагмент с помощью селектора DOM в точности так же, как те, что используются в th:include или th:replace.

10.1 Определение фрагментов в бине представления

Bean-компоненты представления — это bean-компоненты класса org.thymeleaf.spring4.view.ThymeleafView, объявленные в контексте приложения (аннотация Bean, если вы используете конфигурацию Java). Они позволяют задавать фрагменты следующим образом:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("content");
    return view;
}

Учитывая приведенное выше определение bean-компонента, если наш контроллер возвращает content-part (имя вышеупомянутого bean-компонента)…

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "content-part";
}

… Thymeleaf вернет только фрагмент content шаблона индекса — расположение которого, вероятно, будет примерно таким же, как /WEB-INF/templates/index.html, после применения префикса и суффикса. Таким образом, результат будет полностью эквивалентен указанию index :: content:

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    <div th:fragment="content">
      Only this div will be rendered!
    </div>
    ...
  </body>
</html>

Также обратите внимание, что благодаря мощным селекторам разметки Thymeleaf мы можем выделять фрагмент в шаблоне без каких-либо атрибутов th:fragment. Давайте используем атрибут id, например:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("#content");
    return view;
}

10.2 Определение фрагментов в возвращаемом значении контроллера

Вместо декларирования view beans, фрагменты могут быть определены из контроллера с использованием синтаксиса выражений фрагментов (fragment expressions). Просто как в аттрибутах th:insert или th:replace.

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: content";
}

Разумеется, снова доступна вся мощь селекторов DOM, поэтому мы можем выбрать наш фрагмент на основе стандартных атрибутов HTML, таких как id=«content»:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content";
}

И мы также можем использовать параметры, такие как:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content ('myvalue')";
}

11 Продвинутые интеграционные фичи

11.1 Интеграция с RequestDataValueProcessor

Thymeleaf легко интегрируется с интерфейсом Spring RequestDataValueProcessor. Этот интерфейс позволяет перехватывать URL-адреса ссылок, URL-адреса формы и значения полей формы до их записи в результат разметки, а также прозрачно добавлять скрытые поля формы, которые включают функции безопасности, например, такие как: защита против CSRF (подделка межсайтовых запросов).

Реализация RequestDataValueProcessor может быть легко настроена в контексте приложения. Он должен реализовать интерфейс org.springframework.web.servlet.support.RequestDataValueProcessor и иметь requestDataValueProcessor в качестве имени бина:

@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
  return new MyRequestDataValueProcessor();
}

…и Thymeleaf будет использовать это следующим образом:

  • th:href и th:src вызывают RequestDataValueProcessor.processUrl(…) перед отрисовкой URL
  • th:action вызывает RequestDataValueProcessor.processAction(…) перед рендерингом атрибута action формы, и дополнительно он обнаруживает, когда этот атрибут применяется к тегу <form>, который в любом случае должен быть единственным местом, и в этом случае вызывает RequestDataValueProcessor.getExtraHiddenFields(…) и добавляет возвращенные скрытые поля непосредственно перед закрывающим тегом </form>
  • th:value вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения, на которое оно ссылается, если только в том же теге нет th:field (в этом случае th:field сам позаботится)
  • th:field вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения поля, к которому применяется (или тела тега, если им является <textarea>)

Обратите внимание, что существует очень мало сценариев, в которых вам нужно было бы явно реализовать RequestDataValueProcessor в вашем приложении. В большинстве случаев это будет автоматически использоваться библиотеками безопасности, которые вы прозрачно используете, например, например, Spring Security’s CSRF.

11.1 Построение URIs к контроллерам

Начиная с версии 4.1, Spring дает возможность создавать ссылки на аннотированные контроллеры непосредственно из представлений, без необходимости знать URI, на которые эти контроллеры отображаются.

В Thymeleaf это может быть достигнуто с помощью выражения #mvc.url(…), который позволяет задавать методы контроллера заглавными буквами класса контроллера, в котором они находятся, за которым следует имя метода. Это эквивалентно пользовательской функции spring:mvcUrlx(…) в JSP.

Например, для:

public class ExampleController {

    @RequestMapping("/data")
    public String getData(Model model) { ... return "template" }

    @RequestMapping("/data")
    public String getDataParam(@RequestParam String type) { ... return "template" }

}

Следующий код создаст ссылки на методы:

<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get Data Param</a>

Вы можето почитать об этом механизме по ссылке http://docs.spring.io/spring-framework/docs/4.1.2.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views

12 Spring WebFlow интеграция

Интеграционные пакеты Thymeleaf + Spring включают интеграцию с Spring WebFlow (2.3+).

WebFlow включает некоторые возможности AJAX для рендеринга фрагментов отображаемой страницы при срабатывании определенных событий (переходов), и чтобы Thymeleaf мог отслеживать эти запросы AJAX, нам нужно будет использовать другую реализацию ViewResolver, настроенную так:

<bean id="thymeleafViewResolver" class="org.thymeleaf.spring4.view.AjaxThymeleafViewResolver">
    <property name="viewClass" value="org.thymeleaf.spring4.view.FlowAjaxThymeleafView" />
    <property name="templateEngine" ref="templateEngine" />
</bean>

… и тогда этот ViewResolver может быть сконфигурирован в WebFlow ViewFactoryCreator как:

<bean id="mvcViewFactoryCreator" 
      class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
    <property name="viewResolvers" ref="thymeleafViewResolver"/>
</bean>

Отсюда вы можете определить шаблоны Thymeleaf в вашем view-state’s:

<view-state id="detail" view="bookingDetail">
    ...
</view-state>

В приведенном выше примере bookingDetail — это шаблон Thymeleaf, указанный обычным способом, понятный любому из резолверов шаблона, настроенных в TemplateEngine.

12.2 AJAX фрагменты в Spring WebFlow

Обратите внимание, что здесь объясняется только способ создания фрагментов AJAX для использования с Spring WebFlow. Если вы не используете WebFlow, создание контроллера Spring MVC, который отвечает на запрос AJAX и возвращает кусок HTML, так же просто, как и создание любого другого контроллера, возвращающего шаблон, с единственным исключением, что вы, вероятно, будете возвращать фрагмент наподобие «main :: admin» из вашего метода контроллера.

WebFlow позволяет определить отрисовку через AJAX с <render> тегов, например так:

<view-state id="detail" view="bookingDetail">
    <transition on="updateData">
        <render fragments="hoteldata"/>
    </transition>
</view-state>

Эти фрагменты (в данном случае hoteldata) могут представлять собой разделенный запятыми список фрагментов, указанных в разметке с помощью th:fragment:

<div id="data" th:fragment="hoteldata">
    This is a content to be changed
</div>

Всегда помните, что указанные фрагменты должны иметь атрибут id, чтобы библиотеки Spring JavaScript, работающие в браузере, могли заменить разметку.

Теги <render> также можно указывать с помощью селекторов DOM:

<view-state id=«detail» view=«bookingDetail»>
/>

</view-state>

…и это означает, что нет нужды в th:fragment:

<div id="data">
    This is a content to be changed
</div>

Что касается кода, который запускает переход updateData, он выглядит следующим образом:

<script type="text/javascript" th:src="@{/resources/dojo/dojo.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring-Dojo.js}"></script>

  ...

<form id="triggerform" method="post" action="">
    <input type="submit" id="doUpdate" name="_eventId_updateData" value="Update now!" />
</form>

<script type="text/javascript">
    Spring.addDecoration(
        new Spring.AjaxEventDecoration({formId:'triggerform',elementId:'doUpdate',event:'onclick'}));
</script>

Accessing and Debugging Symfony Form Errors

I recently saw an old post on StackOverflow called
How to get form validation errors after binding the request to the form.
It has a lot of answers, most of them are only partially right and a lot
are outdated. So, I wanted to look at the right answer, and why it’s that
way :).

Tip

To see real examples of using forms and customizing form rendering, start
off with our Symfony2 Series (Episode 2 and Episode 4.

Debugging

First, if you’re trying to figure out what errors you have and which field
each is attached to, you should use the
Form::getErrorsAsString()
method that was introduced in Symfony 2.1 (so a long time ago!). Use it temporarily
in a controller to see what’s going on::

public function formAction(Request $request)
{
    $form = $this->createForm(new ProductType());

    $form->handleRequest($request);
    if ($form->isValid()) {
        // ... form handling
    }

    var_dump($form->getErrorsAsString());die;

    $this->render(
        'DemoBundle:Product:form.html.twig',
        array('form' => $form->createView())
    );
}

That’s it. To make things even simpler, you also have the Form panel of
the web debug toolbar in Symfony 2.4. So, debugging form errors

Why $form->getErrors() doesn’t Work

Tip

As of Symfony 2.5, we have a new tool! $form->getErrors(true) will
return all of the errors from all of the fields on your form.

Raise your hand virtually if you’ve tried doing this to debug a form::

public function formAction(Request $request)
{
    $form = $this->createForm(new ProductType());

    $form->handleRequest($request);
    if ($form->isValid()) {
        // ... form handling
    }

    var_dump($form->getErrors());die;

    $this->render(
        'DemoBundle:Product:form.html.twig',
        array('form' => $form->createView())
    );
}

What do you get? Almost definitely an empty array, even when the form has
lots of errors. Yea, I’ve been there too.

The reason is that a form is actually more than just one Form object:
it’s a tree of Form objects. Each field is represented by its own Form
object and the errors for that field are stored on it.

Assuming the form has name and price fields, how can we get the errors
for each field?

$globalErrors = $form->getErrors()
$nameErrors = $form['name']->getErrors();
$priceErrors = $form['price']->getErrors();

By saying $form['name'], we get the Form object that represents just
the name field, which gives us access to just the errors attached to
that field. This means that there’s no one spot to get all of the errors
on your entire form. Saying $form->getErrors() gives you only «global»
errors, i.e. errors that aren’t attached to any specific field (e.g. a CSRF token
failure).

Render all the Errors at the Top of your Form

Tip

As of Symfony 2.5, you can use $form->getErrors(true) to get an array
of all the errors in your form. Yay!

One common question is how you might render all of your errors in one big
list at the top of your form. Again, there’s no one spot where you can
get a big array of all of the errors, so you’d need to build it yourself::

// a simple, but incomplete (see below) way to collect all errors
$errors = array();
foreach ($form as $fieldName => $formField) {
    // each field has an array of errors
    $errors[$fieldName] = $formField->getErrors();
}

We can iterate over $form (a Form object) to get all of its fields.
And again, remember that each field ($formField here), is also a Form
object, which is why we can call
Form::getErrors()
on each.

In reality, since a form can be many-levels deep, this solution isn’t good
enough. Fortunately, someone already posted a more complete one on
Stack Overflow (see the 2.1 version).

From here, you can pass these into your template and render each. Of course,
you’ll need to make sure that you don’t call {{ form_errors() }} on any
of your fields, since you’re printing the errors manually (and remember that
form_row calls form_errors automatically).

Tip

Each field also has an error_bubbling option. If this is set to true
(it defaults to false for most fields), then the error will «bubble»
and attach itself to the parent form. This means that if you set this
option to true for every field, all errors would be attached to
the top-level Form object and could be rendered by calling {{ form_errors(form) }}
in Twig.

Accessing Errors Inside Twig

We can also do some magic in Twig with errors using magical things called
form variables. These guys are absolutely fundamental to customizing
how your forms render.

Tip

If you’re new to form theming and variables and need to master them,
check out Episode 4 of our Symfony series.

Normally, field errors are rendered in Twig by calling form_errors on
each individual field:

{{ form_errors(form) }}

{{ form_label(form.name) }}
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}

Tip

The form_row function calls form_errors internally.

Just like in the controller, the errors are attached to the individual fields
themselves. Hopefully it make sense now why form_errors(form) renders global
errors and form_errors(form.name) renders the errors attached to the
name field.

Tip

Once you’re in Twig, each field (e.g. form, form.name) is an instance
of :symfonyclass:Symfony\Component\Form\FormView.

If you need to customize how the errors are rendered, you can always use
form theming. But you can also do it by leverage form variables:

{{ form_errors(form) }}

{{ form_label(form.name) }}
{{ form_widget(form.name) }}

{% if form.name.vars.errors|length > 0 %}
<ul class="form-errors name">
    {% for error in form.name.vars.errors %}
        {{ error }}
    {% endfor %}
</ul>
{% endif %}

The key here is form.name.vars: a strange array of «variables» that you
have access to on every field. One of the variables you have access to
is errors, but there are many others, like label and required.
Each variable is normally used internally to render the field, but you can
use them manually if you need to:

<label for="form.name.vars.id">
    {{ form.name.vars.label }} {{ form.name.vars.required ? '*' : '' }}
</label>

To find out what variables a field has, just dump them:

{{ dump(form.price.vars) }}

Now that you know about the magic form.vars, you could also use it to render
all validation errors at the top of your form. This will only work for simple forms
(i.e. forms without nested fields), which most forms are:

{% set messages = [] %}
{% for child in form.children %}
    {% for error in child.vars.errors %}
        {% set messages = messages|merge([error.message]) %}
    {% endfor %}
{% endfor %}
{% if messages|length %}
    <ul>
        {% for message in messages %}
            <li>{{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}

Tip

When you are form theming, these variables become accessible in your
form theme template as local variables inside the form blocks (e.g.
simply label or id).

Takeaways

The key lesson is this: each form is a big tree. The top level Form has
children, each which is also a Form object (or a FormView object
when you’re in Twig). If you want to access information about a field (is
it required? what are its errors?), you need to first get access to the child
form and go from there.

Learn More

Stuck on other Symfony topics or want to learn Symfony from the context of
an actual project? Check out our Getting Started with Symfony Series and
cut down on your learning curve!

Возвращает массив со всеми значениями пользовательских полей для определенной записи, пользователя, комментария, термина и так далее.

Функция не очень элегантна и тратит много памяти PHP / SQL-запросов, если вы не используете все значения.

Хуков нет.

Возвращает

(Массив). Ассоциативный массив (field name => field value).

Использование

get_fields( $post_id, $format_value );
$post_id(mixed)
ID записи (пользователя, рубрики и .д.), для которой нужно получить метаполя.
По умолчанию: false (ID текущей записи в цикле)
$format_value(bool)
Применять ли форматирование, указанное в настройках поля.
По умолчанию: true

Примеры

#1 Получим значения метаполей для текущей записи

Отобразим для текущей записи в виде списка все метаполя (имена полей и их значения).

$fields = get_fields();

if( $fields ): ?>
	<ul>
		<?php foreach( $fields as $name => $value ): ?>
			<li><b><?php echo $name; ?></b> <?php echo $value; ?></li>
		<?php endforeach; ?>
	</ul>
<?php endif; ?>

#2 Получим значения для разных сущностей

// Получим значения всех метаполей текущей записи.
$fields = get_fields();

// Получим значения всех метаполей записи с ID = 1.
$post_fields = get_fields( 1 );

// Получим значения всех метаполей пользователя с ID = 2.
$user_fields = get_fields( 'user_2' );

// Получим значения всех метаполей термина с ID = 3.
$term_fields = get_fields( 'term_3' );

// Получим значения всех метаполей термина с таксономией category и ID = 3.
$term_fields = get_fields( 'category_3' );

// Получим значения всех метаполей комментария с ID = 4.
$comment_fields = get_fields( 'comment_4' );

// Получим значения всех метаполей всех страниц опций, созданных ACF.
$option_fields = get_fields( 'options' );

// Или так.
$option_fields = get_fields( 'option' );

#3 Получим значения метаполей для текущей записи без форматирования

В этом примере показано, как получить все поля (имя и значение) без какого-либо форматирования.

Форматирование относится к тому, как изменяются значения после загрузки из базы данных. Например, значение поля изображения сохраняется в базе данных как просто ID вложения, но может быть возвращено в виде URL-адреса, в зависимости от настроек поля.

В некоторых случаях может быть полезно убедиться, что возвращается только необработанное значение, независимо от настроек поля. Для этого мы используем параметр $format_value.

$fields = get_fields( 123, false );
«»» Serializers and ModelSerializers are similar to Forms and ModelForms. Unlike forms, they are not constrained to dealing with HTML output, and form encoded input. Serialization in REST framework is a two-phase process: 1. Serializers marshal between complex types like model instances, and python primitives. 2. The process of marshalling between python primitives and request and response content is handled by parsers and renderers. «»» import contextlib import copy import inspect import traceback from collections import OrderedDict, defaultdict from collections.abc import Mapping from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError from django.db import models from django.db.models.fields import Field as DjangoModelField from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import get_error_detail, set_value from rest_framework.settings import api_settings from rest_framework.utils import html, model_meta, representation from rest_framework.utils.field_mapping import ( ClassLookupDict, get_field_kwargs, get_nested_relation_kwargs, get_relation_kwargs, get_url_kwargs ) from rest_framework.utils.serializer_helpers import ( BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict, ReturnList ) from rest_framework.validators import ( UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator, UniqueTogetherValidator ) # Note: We do the following so that users of the framework can use this style: # # example_field = serializers.CharField(…) # # This helps keep the separation between model fields, form fields, and # serializer fields more explicit. from rest_framework.fields import ( # NOQA # isort:skip BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField, DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField, HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField, ListField, ModelField, MultipleChoiceField, ReadOnlyField, RegexField, SerializerMethodField, SlugField, TimeField, URLField, UUIDField, ) from rest_framework.relations import ( # NOQA # isort:skip HyperlinkedIdentityField, HyperlinkedRelatedField, ManyRelatedField, PrimaryKeyRelatedField, RelatedField, SlugRelatedField, StringRelatedField, ) # Non-field imports, but public API from rest_framework.fields import ( # NOQA # isort:skip CreateOnlyDefault, CurrentUserDefault, SkipField, empty ) from rest_framework.relations import Hyperlink, PKOnlyObject # NOQA # isort:skip # We assume that ‘validators’ are intended for the child serializer, # rather than the parent serializer. LIST_SERIALIZER_KWARGS = ( ‘read_only’, ‘write_only’, ‘required’, ‘default’, ‘initial’, ‘source’, ‘label’, ‘help_text’, ‘style’, ‘error_messages’, ‘allow_empty’, ‘instance’, ‘data’, ‘partial’, ‘context’, ‘allow_null’, ‘max_length’, ‘min_length’ ) ALL_FIELDS = ‘__all__’ # BaseSerializer # ————— class BaseSerializer(Field): «»» The BaseSerializer class provides a minimal class which may be used for writing custom serializer implementations. Note that we strongly restrict the ordering of operations/properties that may be used on the serializer in order to enforce correct usage. In particular, if a `data=` argument is passed then: .is_valid() — Available. .initial_data — Available. .validated_data — Only available after calling `is_valid()` .errors — Only available after calling `is_valid()` .data — Only available after calling `is_valid()` If a `data=` argument is not passed then: .is_valid() — Not available. .initial_data — Not available. .validated_data — Not available. .errors — Not available. .data — Available. «»» def __init__(self, instance=None, data=empty, **kwargs): self.instance = instance if data is not empty: self.initial_data = data self.partial = kwargs.pop(‘partial’, False) self._context = kwargs.pop(‘context’, {}) kwargs.pop(‘many’, None) super().__init__(**kwargs) def __new__(cls, *args, **kwargs): # We override this method in order to automatically create # `ListSerializer` classes instead when `many=True` is set. if kwargs.pop(‘many’, False): return cls.many_init(*args, **kwargs) return super().__new__(cls, *args, **kwargs) # Allow type checkers to make serializers generic. def __class_getitem__(cls, *args, **kwargs): return cls @classmethod def many_init(cls, *args, **kwargs): «»» This method implements the creation of a `ListSerializer` parent class when `many=True` is used. You can customize it if you need to control which keyword arguments are passed to the parent, and which are passed to the child. Note that we’re over-cautious in passing most arguments to both parent and child classes in order to try to cover the general case. If you’re overriding this method you’ll probably want something much simpler, eg: @classmethod def many_init(cls, *args, **kwargs): kwargs[‘child’] = cls() return CustomListSerializer(*args, **kwargs) «»» allow_empty = kwargs.pop(‘allow_empty’, None) max_length = kwargs.pop(‘max_length’, None) min_length = kwargs.pop(‘min_length’, None) child_serializer = cls(*args, **kwargs) list_kwargs = { ‘child’: child_serializer, } if allow_empty is not None: list_kwargs[‘allow_empty’] = allow_empty if max_length is not None: list_kwargs[‘max_length’] = max_length if min_length is not None: list_kwargs[‘min_length’] = min_length list_kwargs.update({ key: value for key, value in kwargs.items() if key in LIST_SERIALIZER_KWARGS }) meta = getattr(cls, ‘Meta’, None) list_serializer_class = getattr(meta, ‘list_serializer_class’, ListSerializer) return list_serializer_class(*args, **list_kwargs) def to_internal_value(self, data): raise NotImplementedError(‘`to_internal_value()` must be implemented.’) def to_representation(self, instance): raise NotImplementedError(‘`to_representation()` must be implemented.’) def update(self, instance, validated_data): raise NotImplementedError(‘`update()` must be implemented.’) def create(self, validated_data): raise NotImplementedError(‘`create()` must be implemented.’) def save(self, **kwargs): assert hasattr(self, ‘_errors’), ( ‘You must call `.is_valid()` before calling `.save()`.’ ) assert not self.errors, ( ‘You cannot call `.save()` on a serializer with invalid data.’ ) # Guard against incorrect use of `serializer.save(commit=False)` assert ‘commit’ not in kwargs, ( «‘commit’ is not a valid keyword argument to the ‘save()’ method. « «If you need to access data before committing to the database then « «inspect ‘serializer.validated_data’ instead. « «You can also pass additional keyword arguments to ‘save()’ if you « «need to set extra attributes on the saved model instance. « «For example: ‘serializer.save(owner=request.user)’.'» ) assert not hasattr(self, ‘_data’), ( «You cannot call `.save()` after accessing `serializer.data`.» «If you need to access data before committing to the database then « «inspect ‘serializer.validated_data’ instead. « ) validated_data = {**self.validated_data, **kwargs} if self.instance is not None: self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( ‘`update()` did not return an object instance.’ ) else: self.instance = self.create(validated_data) assert self.instance is not None, ( ‘`create()` did not return an object instance.’ ) return self.instance def is_valid(self, *, raise_exception=False): assert hasattr(self, ‘initial_data’), ( ‘Cannot call `.is_valid()` as no `data=` keyword argument was ‘ ‘passed when instantiating the serializer instance.’ ) if not hasattr(self, ‘_validated_data’): try: self._validated_data = self.run_validation(self.initial_data) except ValidationError as exc: self._validated_data = {} self._errors = exc.detail else: self._errors = {} if self._errors and raise_exception: raise ValidationError(self.errors) return not bool(self._errors) @property def data(self): if hasattr(self, ‘initial_data’) and not hasattr(self, ‘_validated_data’): msg = ( ‘When a serializer is passed a `data` keyword argument you ‘ ‘must call `.is_valid()` before attempting to access the ‘ ‘serialized `.data` representation.n ‘You should either call `.is_valid()` first, ‘ ‘or access `.initial_data` instead.’ ) raise AssertionError(msg) if not hasattr(self, ‘_data’): if self.instance is not None and not getattr(self, ‘_errors’, None): self._data = self.to_representation(self.instance) elif hasattr(self, ‘_validated_data’) and not getattr(self, ‘_errors’, None): self._data = self.to_representation(self.validated_data) else: self._data = self.get_initial() return self._data @property def errors(self): if not hasattr(self, ‘_errors’): msg = ‘You must call `.is_valid()` before accessing `.errors`.’ raise AssertionError(msg) return self._errors @property def validated_data(self): if not hasattr(self, ‘_validated_data’): msg = ‘You must call `.is_valid()` before accessing `.validated_data`.’ raise AssertionError(msg) return self._validated_data # Serializer & ListSerializer classes # ———————————— class SerializerMetaclass(type): «»» This metaclass sets a dictionary named `_declared_fields` on the class. Any instances of `Field` included as attributes on either the class or on any of its superclasses will be include in the `_declared_fields` dictionary. «»» @classmethod def _get_declared_fields(cls, bases, attrs): fields = [(field_name, attrs.pop(field_name)) for field_name, obj in list(attrs.items()) if isinstance(obj, Field)] fields.sort(key=lambda x: x[1]._creation_counter) # Ensures a base class field doesn’t override cls attrs, and maintains # field precedence when inheriting multiple parents. e.g. if there is a # class C(A, B), and A and B both define ‘field’, use ‘field’ from A. known = set(attrs) def visit(name): known.add(name) return name base_fields = [ (visit(name), f) for base in bases if hasattr(base, ‘_declared_fields’) for name, f in base._declared_fields.items() if name not in known ] return OrderedDict(base_fields + fields) def __new__(cls, name, bases, attrs): attrs[‘_declared_fields’] = cls._get_declared_fields(bases, attrs) return super().__new__(cls, name, bases, attrs) def as_serializer_error(exc): assert isinstance(exc, (ValidationError, DjangoValidationError)) if isinstance(exc, DjangoValidationError): detail = get_error_detail(exc) else: detail = exc.detail if isinstance(detail, Mapping): # If errors may be a dict we use the standard {key: list of values}. # Here we ensure that all the values are *lists* of errors. return { key: value if isinstance(value, (list, Mapping)) else [value] for key, value in detail.items() } elif isinstance(detail, list): # Errors raised as a list are non-field errors. return { api_settings.NON_FIELD_ERRORS_KEY: detail } # Errors raised as a string are non-field errors. return { api_settings.NON_FIELD_ERRORS_KEY: [detail] } class Serializer(BaseSerializer, metaclass=SerializerMetaclass): default_error_messages = { ‘invalid’: _(‘Invalid data. Expected a dictionary, but got {datatype}.’) } @cached_property def fields(self): «»» A dictionary of {field_name: field_instance}. «»» # `fields` is evaluated lazily. We do this to ensure that we don’t # have issues importing modules that use ModelSerializers as fields, # even if Django’s app-loading stage has not yet run. fields = BindingDict(self) for key, value in self.get_fields().items(): fields[key] = value return fields @property def _writable_fields(self): for field in self.fields.values(): if not field.read_only: yield field @property def _readable_fields(self): for field in self.fields.values(): if not field.write_only: yield field def get_fields(self): «»» Returns a dictionary of {field_name: field_instance}. «»» # Every new serializer is created with a clone of the field instances. # This allows users to dynamically modify the fields on a serializer # instance without affecting every other serializer instance. return copy.deepcopy(self._declared_fields) def get_validators(self): «»» Returns a list of validator callables. «»» # Used by the lazily-evaluated `validators` property. meta = getattr(self, ‘Meta’, None) validators = getattr(meta, ‘validators’, None) return list(validators) if validators else [] def get_initial(self): if hasattr(self, ‘initial_data’): # initial_data may not be a valid type if not isinstance(self.initial_data, Mapping): return OrderedDict() return OrderedDict([ (field_name, field.get_value(self.initial_data)) for field_name, field in self.fields.items() if (field.get_value(self.initial_data) is not empty) and not field.read_only ]) return OrderedDict([ (field.field_name, field.get_initial()) for field in self.fields.values() if not field.read_only ]) def get_value(self, dictionary): # We override the default field access in order to support # nested HTML forms. if html.is_html_input(dictionary): return html.parse_html_dict(dictionary, prefix=self.field_name) or empty return dictionary.get(self.field_name, empty) def run_validation(self, data=empty): «»» We override the default `run_validation`, because the validation performed by validators and the `.validate()` method should be coerced into an error dictionary with a ‘non_fields_error’ key. «»» (is_empty_value, data) = self.validate_empty_values(data) if is_empty_value: return data value = self.to_internal_value(data) try: self.run_validators(value) value = self.validate(value) assert value is not None, ‘.validate() should return the validated data’ except (ValidationError, DjangoValidationError) as exc: raise ValidationError(detail=as_serializer_error(exc)) return value def _read_only_defaults(self): fields = [ field for field in self.fields.values() if (field.read_only) and (field.default != empty) and (field.source != ‘*’) and (‘.’ not in field.source) ] defaults = OrderedDict() for field in fields: try: default = field.get_default() except SkipField: continue defaults[field.source] = default return defaults def run_validators(self, value): «»» Add read_only fields with defaults to value before running validators. «»» if isinstance(value, dict): to_validate = self._read_only_defaults() to_validate.update(value) else: to_validate = value super().run_validators(to_validate) def to_internal_value(self, data): «»» Dict of native values <- Dict of primitive datatypes. «»» if not isinstance(data, Mapping): message = self.error_messages[‘invalid’].format( datatype=type(data).__name__ ) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code=‘invalid’) ret = OrderedDict() errors = OrderedDict() fields = self._writable_fields for field in fields: validate_method = getattr(self, ‘validate_’ + field.field_name, None) primitive_value = field.get_value(data) try: validated_value = field.run_validation(primitive_value) if validate_method is not None: validated_value = validate_method(validated_value) except ValidationError as exc: errors[field.field_name] = exc.detail except DjangoValidationError as exc: errors[field.field_name] = get_error_detail(exc) except SkipField: pass else: set_value(ret, field.source_attrs, validated_value) if errors: raise ValidationError(errors) return ret def to_representation(self, instance): «»» Object instance -> Dict of primitive datatypes. «»» ret = OrderedDict() fields = self._readable_fields for field in fields: try: attribute = field.get_attribute(instance) except SkipField: continue # We skip `to_representation` for `None` values so that fields do # not have to explicitly deal with that case. # # For related fields with `use_pk_only_optimization` we need to # resolve the pk value. check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute if check_for_none is None: ret[field.field_name] = None else: ret[field.field_name] = field.to_representation(attribute) return ret def validate(self, attrs): return attrs def __repr__(self): return representation.serializer_repr(self, indent=1) # The following are used for accessing `BoundField` instances on the # serializer, for the purposes of presenting a form-like API onto the # field values and field errors. def __iter__(self): for field in self.fields.values(): yield self[field.field_name] def __getitem__(self, key): field = self.fields[key] value = self.data.get(key) error = self.errors.get(key) if hasattr(self, ‘_errors’) else None if isinstance(field, Serializer): return NestedBoundField(field, value, error) if isinstance(field, JSONField): return JSONBoundField(field, value, error) return BoundField(field, value, error) # Include a backlink to the serializer class on return objects. # Allows renderers such as HTMLFormRenderer to get the full field info. @property def data(self): ret = super().data return ReturnDict(ret, serializer=self) @property def errors(self): ret = super().errors if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], ‘code’, None) == ‘null’: # Edge case. Provide a more descriptive error than # «this field may not be null», when no data is passed. detail = ErrorDetail(‘No data provided’, code=‘null’) ret = {api_settings.NON_FIELD_ERRORS_KEY: [detail]} return ReturnDict(ret, serializer=self) # There’s some replication of `ListField` here, # but that’s probably better than obfuscating the call hierarchy. class ListSerializer(BaseSerializer): child = None many = True default_error_messages = { ‘not_a_list’: _(‘Expected a list of items but got type «{input_type}».’), ’empty’: _(‘This list may not be empty.’), ‘max_length’: _(‘Ensure this field has no more than {max_length} elements.’), ‘min_length’: _(‘Ensure this field has at least {min_length} elements.’) } def __init__(self, *args, **kwargs): self.child = kwargs.pop(‘child’, copy.deepcopy(self.child)) self.allow_empty = kwargs.pop(‘allow_empty’, True) self.max_length = kwargs.pop(‘max_length’, None) self.min_length = kwargs.pop(‘min_length’, None) assert self.child is not None, ‘`child` is a required argument.’ assert not inspect.isclass(self.child), ‘`child` has not been instantiated.’ super().__init__(*args, **kwargs) self.child.bind(field_name=», parent=self) def get_initial(self): if hasattr(self, ‘initial_data’): return self.to_representation(self.initial_data) return [] def get_value(self, dictionary): «»» Given the input dictionary, return the field value. «»» # We override the default field access in order to support # lists in HTML forms. if html.is_html_input(dictionary): return html.parse_html_list(dictionary, prefix=self.field_name, default=empty) return dictionary.get(self.field_name, empty) def run_validation(self, data=empty): «»» We override the default `run_validation`, because the validation performed by validators and the `.validate()` method should be coerced into an error dictionary with a ‘non_fields_error’ key. «»» (is_empty_value, data) = self.validate_empty_values(data) if is_empty_value: return data value = self.to_internal_value(data) try: self.run_validators(value) value = self.validate(value) assert value is not None, ‘.validate() should return the validated data’ except (ValidationError, DjangoValidationError) as exc: raise ValidationError(detail=as_serializer_error(exc)) return value def to_internal_value(self, data): «»» List of dicts of native values <- List of dicts of primitive datatypes. «»» if html.is_html_input(data): data = html.parse_html_list(data, default=[]) if not isinstance(data, list): message = self.error_messages[‘not_a_list’].format( input_type=type(data).__name__ ) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code=‘not_a_list’) if not self.allow_empty and len(data) == 0: message = self.error_messages[’empty’] raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code=’empty’) if self.max_length is not None and len(data) > self.max_length: message = self.error_messages[‘max_length’].format(max_length=self.max_length) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code=‘max_length’) if self.min_length is not None and len(data) < self.min_length: message = self.error_messages[‘min_length’].format(min_length=self.min_length) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }, code=‘min_length’) ret = [] errors = [] for item in data: try: validated = self.child.run_validation(item) except ValidationError as exc: errors.append(exc.detail) else: ret.append(validated) errors.append({}) if any(errors): raise ValidationError(errors) return ret def to_representation(self, data): «»» List of object instances -> List of dicts of primitive datatypes. «»» # Dealing with nested relationships, data can be a Manager, # so, first get a queryset from the Manager if needed iterable = data.all() if isinstance(data, models.manager.BaseManager) else data return [ self.child.to_representation(item) for item in iterable ] def validate(self, attrs): return attrs def update(self, instance, validated_data): raise NotImplementedError( «Serializers with many=True do not support multiple update by « «default, only multiple create. For updates it is unclear how to « «deal with insertions and deletions. If you need to support « «multiple update, use a `ListSerializer` class and override « «`.update()` so you can specify the behavior exactly.» ) def create(self, validated_data): return [ self.child.create(attrs) for attrs in validated_data ] def save(self, **kwargs): «»» Save and return a list of object instances. «»» # Guard against incorrect use of `serializer.save(commit=False)` assert ‘commit’ not in kwargs, ( «‘commit’ is not a valid keyword argument to the ‘save()’ method. « «If you need to access data before committing to the database then « «inspect ‘serializer.validated_data’ instead. « «You can also pass additional keyword arguments to ‘save()’ if you « «need to set extra attributes on the saved model instance. « «For example: ‘serializer.save(owner=request.user)’.'» ) validated_data = [ {**attrs, **kwargs} for attrs in self.validated_data ] if self.instance is not None: self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( ‘`update()` did not return an object instance.’ ) else: self.instance = self.create(validated_data) assert self.instance is not None, ( ‘`create()` did not return an object instance.’ ) return self.instance def is_valid(self, *, raise_exception=False): # This implementation is the same as the default, # except that we use lists, rather than dicts, as the empty case. assert hasattr(self, ‘initial_data’), ( ‘Cannot call `.is_valid()` as no `data=` keyword argument was ‘ ‘passed when instantiating the serializer instance.’ ) if not hasattr(self, ‘_validated_data’): try: self._validated_data = self.run_validation(self.initial_data) except ValidationError as exc: self._validated_data = [] self._errors = exc.detail else: self._errors = [] if self._errors and raise_exception: raise ValidationError(self.errors) return not bool(self._errors) def __repr__(self): return representation.list_repr(self, indent=1) # Include a backlink to the serializer class on return objects. # Allows renderers such as HTMLFormRenderer to get the full field info. @property def data(self): ret = super().data return ReturnList(ret, serializer=self) @property def errors(self): ret = super().errors if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], ‘code’, None) == ‘null’: # Edge case. Provide a more descriptive error than # «this field may not be null», when no data is passed. detail = ErrorDetail(‘No data provided’, code=‘null’) ret = {api_settings.NON_FIELD_ERRORS_KEY: [detail]} if isinstance(ret, dict): return ReturnDict(ret, serializer=self) return ReturnList(ret, serializer=self) # ModelSerializer & HyperlinkedModelSerializer # ——————————————— def raise_errors_on_nested_writes(method_name, serializer, validated_data): «»» Give explicit errors when users attempt to pass writable nested data. If we don’t do this explicitly they’d get a less helpful error when calling `.save()` on the serializer. We don’t *automatically* support these sorts of nested writes because there are too many ambiguities to define a default behavior. Eg. Suppose we have a `UserSerializer` with a nested profile. How should we handle the case of an update, where the `profile` relationship does not exist? Any of the following might be valid: * Raise an application error. * Silently ignore the nested part of the update. * Automatically create a profile instance. «»» ModelClass = serializer.Meta.model model_field_info = model_meta.get_field_info(ModelClass) # Ensure we don’t have a writable nested field. For example: # # class UserSerializer(ModelSerializer): # … # profile = ProfileSerializer() assert not any( isinstance(field, BaseSerializer) and (field.source in validated_data) and (field.source in model_field_info.relations) and isinstance(validated_data[field.source], (list, dict)) for field in serializer._writable_fields ), ( ‘The `.{method_name}()` method does not support writable nested ‘ ‘fields by default.nWrite an explicit `.{method_name}()` method for ‘ ‘serializer `{module}.{class_name}`, or set `read_only=True` on ‘ ‘nested serializer fields.’.format( method_name=method_name, module=serializer.__class__.__module__, class_name=serializer.__class__.__name__ ) ) # Ensure we don’t have a writable dotted-source field. For example: # # class UserSerializer(ModelSerializer): # … # address = serializer.CharField(‘profile.address’) # # Though, non-relational fields (e.g., JSONField) are acceptable. For example: # # class NonRelationalPersonModel(models.Model): # profile = JSONField() # # class UserSerializer(ModelSerializer): # … # address = serializer.CharField(‘profile.address’) assert not any( len(field.source_attrs) > 1 and (field.source_attrs[0] in validated_data) and (field.source_attrs[0] in model_field_info.relations) and isinstance(validated_data[field.source_attrs[0]], (list, dict)) for field in serializer._writable_fields ), ( ‘The `.{method_name}()` method does not support writable dotted-source ‘ ‘fields by default.nWrite an explicit `.{method_name}()` method for ‘ ‘serializer `{module}.{class_name}`, or set `read_only=True` on ‘ ‘dotted-source serializer fields.’.format( method_name=method_name, module=serializer.__class__.__module__, class_name=serializer.__class__.__name__ ) ) class ModelSerializer(Serializer): «»» A `ModelSerializer` is just a regular `Serializer`, except that: * A set of default fields are automatically populated. * A set of default validators are automatically populated. * Default `.create()` and `.update()` implementations are provided. The process of automatically determining a set of serializer fields based on the model fields is reasonably complex, but you almost certainly don’t need to dig into the implementation. If the `ModelSerializer` class *doesn’t* generate the set of fields that you need you should either declare the extra/differing fields explicitly on the serializer class, or simply use a `Serializer` class. «»» serializer_field_mapping = { models.AutoField: IntegerField, models.BigIntegerField: IntegerField, models.BooleanField: BooleanField, models.CharField: CharField, models.CommaSeparatedIntegerField: CharField, models.DateField: DateField, models.DateTimeField: DateTimeField, models.DecimalField: DecimalField, models.DurationField: DurationField, models.EmailField: EmailField, models.Field: ModelField, models.FileField: FileField, models.FloatField: FloatField, models.ImageField: ImageField, models.IntegerField: IntegerField, models.NullBooleanField: BooleanField, models.PositiveIntegerField: IntegerField, models.PositiveSmallIntegerField: IntegerField, models.SlugField: SlugField, models.SmallIntegerField: IntegerField, models.TextField: CharField, models.TimeField: TimeField, models.URLField: URLField, models.UUIDField: UUIDField, models.GenericIPAddressField: IPAddressField, models.FilePathField: FilePathField, } if hasattr(models, ‘JSONField’): serializer_field_mapping[models.JSONField] = JSONField if postgres_fields: serializer_field_mapping[postgres_fields.HStoreField] = HStoreField serializer_field_mapping[postgres_fields.ArrayField] = ListField serializer_field_mapping[postgres_fields.JSONField] = JSONField serializer_related_field = PrimaryKeyRelatedField serializer_related_to_field = SlugRelatedField serializer_url_field = HyperlinkedIdentityField serializer_choice_field = ChoiceField # The field name for hyperlinked identity fields. Defaults to ‘url’. # You can modify this using the API setting. # # Note that if you instead need modify this on a per-serializer basis, # you’ll also need to ensure you update the `create` method on any generic # views, to correctly handle the ‘Location’ response header for # «HTTP 201 Created» responses. url_field_name = None # Default `create` and `update` behavior… def create(self, validated_data): «»» We have a bit of extra checking around this in order to provide descriptive messages when something goes wrong, but this method is essentially just: return ExampleModel.objects.create(**validated_data) If there are many to many fields present on the instance then they cannot be set until the model is instantiated, in which case the implementation is like so: example_relationship = validated_data.pop(‘example_relationship’) instance = ExampleModel.objects.create(**validated_data) instance.example_relationship = example_relationship return instance The default implementation also does not handle nested relationships. If you want to support writable nested relationships you’ll need to write an explicit `.create()` method. «»» raise_errors_on_nested_writes(‘create’, self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass._default_manager.create(**validated_data) except TypeError: tb = traceback.format_exc() msg = ( ‘Got a `TypeError` when calling `%s.%s.create()`. ‘ ‘This may be because you have a writable field on the ‘ ‘serializer class that is not a valid argument to ‘ ‘`%s.%s.create()`. You may need to make the field ‘ ‘read-only, or override the %s.create() method to handle ‘ ‘this correctly.nOriginal exception was:n %s’ % ( ModelClass.__name__, ModelClass._default_manager.name, ModelClass.__name__, ModelClass._default_manager.name, self.__class__.__name__, tb ) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): field = getattr(instance, field_name) field.set(value) return instance def update(self, instance, validated_data): raise_errors_on_nested_writes(‘update’, self, validated_data) info = model_meta.get_field_info(instance) # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don’t need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) instance.save() # Note that many-to-many fields are set after updating instance. # Setting m2m fields triggers signals which could potentially change # updated instance and we do not want it to collide with .update() for attr, value in m2m_fields: field = getattr(instance, attr) field.set(value) return instance # Determine the fields to apply… def get_fields(self): «»» Return the dict of field names -> field instances that should be used for `self.fields` when instantiating the serializer. «»» if self.url_field_name is None: self.url_field_name = api_settings.URL_FIELD_NAME assert hasattr(self, ‘Meta’), ( ‘Class {serializer_class} missing «Meta» attribute’.format( serializer_class=self.__class__.__name__ ) ) assert hasattr(self.Meta, ‘model’), ( ‘Class {serializer_class} missing «Meta.model» attribute’.format( serializer_class=self.__class__.__name__ ) ) if model_meta.is_abstract_model(self.Meta.model): raise ValueError( ‘Cannot use ModelSerializer with Abstract Models.’ ) declared_fields = copy.deepcopy(self._declared_fields) model = getattr(self.Meta, ‘model’) depth = getattr(self.Meta, ‘depth’, 0) if depth is not None: assert depth >= 0, «‘depth’ may not be negative.» assert depth <= 10, «‘depth’ may not be greater than 10.» # Retrieve metadata about fields & relationships on the model class. info = model_meta.get_field_info(model) field_names = self.get_field_names(declared_fields, info) # Determine any extra field arguments and hidden fields that # should be included extra_kwargs = self.get_extra_kwargs() extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs( field_names, declared_fields, extra_kwargs ) # Determine the fields that should be included on the serializer. fields = OrderedDict() for field_name in field_names: # If the field is explicitly declared on the class then use that. if field_name in declared_fields: fields[field_name] = declared_fields[field_name] continue extra_field_kwargs = extra_kwargs.get(field_name, {}) source = extra_field_kwargs.get(‘source’, ‘*’) if source == ‘*’: source = field_name # Determine the serializer field class and keyword arguments. field_class, field_kwargs = self.build_field( source, info, model, depth ) # Include any kwargs defined in `Meta.extra_kwargs` field_kwargs = self.include_extra_kwargs( field_kwargs, extra_field_kwargs ) # Create the serializer field. fields[field_name] = field_class(**field_kwargs) # Add in any hidden fields. fields.update(hidden_fields) return fields # Methods for determining the set of field names to include… def get_field_names(self, declared_fields, info): «»» Returns the list of all field names that should be created when instantiating this serializer class. This is based on the default set of fields, but also takes into account the `Meta.fields` or `Meta.exclude` options if they have been specified. «»» fields = getattr(self.Meta, ‘fields’, None) exclude = getattr(self.Meta, ‘exclude’, None) if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)): raise TypeError( ‘The `fields` option must be a list or tuple or «__all__». ‘ ‘Got %s.’ % type(fields).__name__ ) if exclude and not isinstance(exclude, (list, tuple)): raise TypeError( ‘The `exclude` option must be a list or tuple. Got %s.’ % type(exclude).__name__ ) assert not (fields and exclude), ( «Cannot set both ‘fields’ and ‘exclude’ options on « «serializer {serializer_class}.».format( serializer_class=self.__class__.__name__ ) ) assert not (fields is None and exclude is None), ( «Creating a ModelSerializer without either the ‘fields’ attribute « «or the ‘exclude’ attribute has been deprecated since 3.3.0, « «and is now disallowed. Add an explicit fields = ‘__all__’ to the « «{serializer_class} serializer.».format( serializer_class=self.__class__.__name__ ), ) if fields == ALL_FIELDS: fields = None if fields is not None: # Ensure that all declared fields have also been included in the # `Meta.fields` option. # Do not require any fields that are declared in a parent class, # in order to allow serializer subclasses to only include # a subset of fields. required_field_names = set(declared_fields) for cls in self.__class__.__bases__: required_field_names -= set(getattr(cls, ‘_declared_fields’, [])) for field_name in required_field_names: assert field_name in fields, ( «The field ‘{field_name}’ was declared on serializer « «{serializer_class}, but has not been included in the « «‘fields’ option.».format( field_name=field_name, serializer_class=self.__class__.__name__ ) ) return fields # Use the default set of field names if `Meta.fields` is not specified. fields = self.get_default_field_names(declared_fields, info) if exclude is not None: # If `Meta.exclude` is included, then remove those fields. for field_name in exclude: assert field_name not in self._declared_fields, ( «Cannot both declare the field ‘{field_name}’ and include « «it in the {serializer_class} ‘exclude’ option. Remove the « «field or, if inherited from a parent serializer, disable « «with `{field_name} = None`.» .format( field_name=field_name, serializer_class=self.__class__.__name__ ) ) assert field_name in fields, ( «The field ‘{field_name}’ was included on serializer « «{serializer_class} in the ‘exclude’ option, but does « «not match any model field.».format( field_name=field_name, serializer_class=self.__class__.__name__ ) ) fields.remove(field_name) return fields def get_default_field_names(self, declared_fields, model_info): «»» Return the default list of field names that will be used if the `Meta.fields` option is not specified. «»» return ( [model_info.pk.name] + list(declared_fields) + list(model_info.fields) + list(model_info.forward_relations) ) # Methods for constructing serializer fields… def build_field(self, field_name, info, model_class, nested_depth): «»» Return a two tuple of (cls, kwargs) to build a serializer field with. «»» if field_name in info.fields_and_pk: model_field = info.fields_and_pk[field_name] return self.build_standard_field(field_name, model_field) elif field_name in info.relations: relation_info = info.relations[field_name] if not nested_depth: return self.build_relational_field(field_name, relation_info) else: return self.build_nested_field(field_name, relation_info, nested_depth) elif hasattr(model_class, field_name): return self.build_property_field(field_name, model_class) elif field_name == self.url_field_name: return self.build_url_field(field_name, model_class) return self.build_unknown_field(field_name, model_class) def build_standard_field(self, field_name, model_field): «»» Create regular model fields. «»» field_mapping = ClassLookupDict(self.serializer_field_mapping) field_class = field_mapping[model_field] field_kwargs = get_field_kwargs(field_name, model_field) # Special case to handle when a OneToOneField is also the primary key if model_field.one_to_one and model_field.primary_key: field_class = self.serializer_related_field field_kwargs[‘queryset’] = model_field.related_model.objects if ‘choices’ in field_kwargs: # Fields with choices get coerced into `ChoiceField` # instead of using their regular typed field. field_class = self.serializer_choice_field # Some model fields may introduce kwargs that would not be valid # for the choice field. We need to strip these out. # Eg. models.DecimalField(max_digits=3, decimal_places=1, choices=DECIMAL_CHOICES) valid_kwargs = { ‘read_only’, ‘write_only’, ‘required’, ‘default’, ‘initial’, ‘source’, ‘label’, ‘help_text’, ‘style’, ‘error_messages’, ‘validators’, ‘allow_null’, ‘allow_blank’, ‘choices’ } for key in list(field_kwargs): if key not in valid_kwargs: field_kwargs.pop(key) if not issubclass(field_class, ModelField): # `model_field` is only valid for the fallback case of # `ModelField`, which is used when no other typed field # matched to the model field. field_kwargs.pop(‘model_field’, None) if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField): # `allow_blank` is only valid for textual fields. field_kwargs.pop(‘allow_blank’, None) is_django_jsonfield = hasattr(models, ‘JSONField’) and isinstance(model_field, models.JSONField) if (postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or is_django_jsonfield: # Populate the `encoder` argument of `JSONField` instances generated # for the model `JSONField`. field_kwargs[‘encoder’] = getattr(model_field, ‘encoder’, None) if is_django_jsonfield: field_kwargs[‘decoder’] = getattr(model_field, ‘decoder’, None) if postgres_fields and isinstance(model_field, postgres_fields.ArrayField): # Populate the `child` argument on `ListField` instances generated # for the PostgreSQL specific `ArrayField`. child_model_field = model_field.base_field child_field_class, child_field_kwargs = self.build_standard_field( ‘child’, child_model_field ) field_kwargs[‘child’] = child_field_class(**child_field_kwargs) return field_class, field_kwargs def build_relational_field(self, field_name, relation_info): «»» Create fields for forward and reverse relationships. «»» field_class = self.serializer_related_field field_kwargs = get_relation_kwargs(field_name, relation_info) to_field = field_kwargs.pop(‘to_field’, None) if to_field and not relation_info.reverse and not relation_info.related_model._meta.get_field(to_field).primary_key: field_kwargs[‘slug_field’] = to_field field_class = self.serializer_related_to_field # `view_name` is only valid for hyperlinked relationships. if not issubclass(field_class, HyperlinkedRelatedField): field_kwargs.pop(‘view_name’, None) return field_class, field_kwargs def build_nested_field(self, field_name, relation_info, nested_depth): «»» Create nested fields for forward and reverse relationships. «»» class NestedSerializer(ModelSerializer): class Meta: model = relation_info.related_model depth = nested_depth 1 fields = ‘__all__’ field_class = NestedSerializer field_kwargs = get_nested_relation_kwargs(relation_info) return field_class, field_kwargs def build_property_field(self, field_name, model_class): «»» Create a read only field for model methods and properties. «»» field_class = ReadOnlyField field_kwargs = {} return field_class, field_kwargs def build_url_field(self, field_name, model_class): «»» Create a field representing the object’s own URL. «»» field_class = self.serializer_url_field field_kwargs = get_url_kwargs(model_class) return field_class, field_kwargs def build_unknown_field(self, field_name, model_class): «»» Raise an error on any unknown fields. «»» raise ImproperlyConfigured( ‘Field name `%s` is not valid for model `%s`.’ % (field_name, model_class.__name__) ) def include_extra_kwargs(self, kwargs, extra_kwargs): «»» Include any ‘extra_kwargs’ that have been included for this field, possibly removing any incompatible existing keyword arguments. «»» if extra_kwargs.get(‘read_only’, False): for attr in [ ‘required’, ‘default’, ‘allow_blank’, ‘min_length’, ‘max_length’, ‘min_value’, ‘max_value’, ‘validators’, ‘queryset’ ]: kwargs.pop(attr, None) if extra_kwargs.get(‘default’) and kwargs.get(‘required’) is False: kwargs.pop(‘required’) if extra_kwargs.get(‘read_only’, kwargs.get(‘read_only’, False)): extra_kwargs.pop(‘required’, None) # Read only fields should always omit the ‘required’ argument. kwargs.update(extra_kwargs) return kwargs # Methods for determining additional keyword arguments to apply… def get_extra_kwargs(self): «»» Return a dictionary mapping field names to a dictionary of additional keyword arguments. «»» extra_kwargs = copy.deepcopy(getattr(self.Meta, ‘extra_kwargs’, {})) read_only_fields = getattr(self.Meta, ‘read_only_fields’, None) if read_only_fields is not None: if not isinstance(read_only_fields, (list, tuple)): raise TypeError( ‘The `read_only_fields` option must be a list or tuple. ‘ ‘Got %s.’ % type(read_only_fields).__name__ ) for field_name in read_only_fields: kwargs = extra_kwargs.get(field_name, {}) kwargs[‘read_only’] = True extra_kwargs[field_name] = kwargs else: # Guard against the possible misspelling `readonly_fields` (used # by the Django admin and others). assert not hasattr(self.Meta, ‘readonly_fields’), ( ‘Serializer `%s.%s` has field `readonly_fields`; ‘ ‘the correct spelling for the option is `read_only_fields`.’ % (self.__class__.__module__, self.__class__.__name__) ) return extra_kwargs def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs): «»» Return any additional field options that need to be included as a result of uniqueness constraints on the model. This is returned as a two-tuple of: (‘dict of updated extra kwargs’, ‘mapping of hidden fields’) «»» if getattr(self.Meta, ‘validators’, None) is not None: return (extra_kwargs, {}) model = getattr(self.Meta, ‘model’) model_fields = self._get_model_fields( field_names, declared_fields, extra_kwargs ) # Determine if we need any additional `HiddenField` or extra keyword # arguments to deal with `unique_for` dates that are required to # be in the input data in order to validate it. unique_constraint_names = set() for model_field in model_fields.values(): # Include each of the `unique_for_*` field names. unique_constraint_names |= {model_field.unique_for_date, model_field.unique_for_month, model_field.unique_for_year} unique_constraint_names -= {None} # Include each of the `unique_together` field names, # so long as all the field names are included on the serializer. for parent_class in [model] + list(model._meta.parents): for unique_together_list in parent_class._meta.unique_together: if set(field_names).issuperset(unique_together_list): unique_constraint_names |= set(unique_together_list) # Now we have all the field names that have uniqueness constraints # applied, we can add the extra ‘required=…’ or ‘default=…’ # arguments that are appropriate to these fields, or add a `HiddenField` for it. hidden_fields = {} uniqueness_extra_kwargs = {} for unique_constraint_name in unique_constraint_names: # Get the model field that is referred too. unique_constraint_field = model._meta.get_field(unique_constraint_name) if getattr(unique_constraint_field, ‘auto_now_add’, None): default = CreateOnlyDefault(timezone.now) elif getattr(unique_constraint_field, ‘auto_now’, None): default = timezone.now elif unique_constraint_field.has_default(): default = unique_constraint_field.default else: default = empty if unique_constraint_name in model_fields: # The corresponding field is present in the serializer if default is empty: uniqueness_extra_kwargs[unique_constraint_name] = {‘required’: True} else: uniqueness_extra_kwargs[unique_constraint_name] = {‘default’: default} elif default is not empty: # The corresponding field is not present in the # serializer. We have a default to use for it, so # add in a hidden field that populates it. hidden_fields[unique_constraint_name] = HiddenField(default=default) # Update `extra_kwargs` with any new options. for key, value in uniqueness_extra_kwargs.items(): if key in extra_kwargs: value.update(extra_kwargs[key]) extra_kwargs[key] = value return extra_kwargs, hidden_fields def _get_model_fields(self, field_names, declared_fields, extra_kwargs): «»» Returns all the model fields that are being mapped to by fields on the serializer class. Returned as a dict of ‘model field name’ -> ‘model field’. Used internally by `get_uniqueness_field_options`. «»» model = getattr(self.Meta, ‘model’) model_fields = {} for field_name in field_names: if field_name in declared_fields: # If the field is declared on the serializer field = declared_fields[field_name] source = field.source or field_name else: try: source = extra_kwargs[field_name][‘source’] except KeyError: source = field_name if ‘.’ in source or source == ‘*’: # Model fields will always have a simple source mapping, # they can’t be nested attribute lookups. continue with contextlib.suppress(FieldDoesNotExist): field = model._meta.get_field(source) if isinstance(field, DjangoModelField): model_fields[source] = field return model_fields # Determine the validators to apply… def get_validators(self): «»» Determine the set of validators to use when instantiating serializer. «»» # If the validators have been declared explicitly then use that. validators = getattr(getattr(self, ‘Meta’, None), ‘validators’, None) if validators is not None: return list(validators) # Otherwise use the default set of validators. return ( self.get_unique_together_validators() + self.get_unique_for_date_validators() ) def get_unique_together_validators(self): «»» Determine a default set of validators for any unique_together constraints. «»» model_class_inheritance_tree = ( [self.Meta.model] + list(self.Meta.model._meta.parents) ) # The field names we’re passing though here only include fields # which may map onto a model field. Any dotted field name lookups # cannot map to a field, and must be a traversal, so we’re not # including those. field_sources = OrderedDict( (field.field_name, field.source) for field in self._writable_fields if (field.source != ‘*’) and (‘.’ not in field.source) ) # Special Case: Add read_only fields with defaults. field_sources.update(OrderedDict( (field.field_name, field.source) for field in self.fields.values() if (field.read_only) and (field.default != empty) and (field.source != ‘*’) and (‘.’ not in field.source) )) # Invert so we can find the serializer field names that correspond to # the model field names in the unique_together sets. This also allows # us to check that multiple fields don’t map to the same source. source_map = defaultdict(list) for name, source in field_sources.items(): source_map[source].append(name) # Note that we make sure to check `unique_together` both on the # base model class, but also on any parent classes. validators = [] for parent_class in model_class_inheritance_tree: for unique_together in parent_class._meta.unique_together: # Skip if serializer does not map to all unique together sources if not set(source_map).issuperset(unique_together): continue for source in unique_together: assert len(source_map[source]) == 1, ( «Unable to create `UniqueTogetherValidator` for « «`{model}.{field}` as `{serializer}` has multiple « «fields ({fields}) that map to this model field. « «Either remove the extra fields, or override « «`Meta.validators` with a `UniqueTogetherValidator` « «using the desired field names.» .format( model=self.Meta.model.__name__, serializer=self.__class__.__name__, field=source, fields=‘, ‘.join(source_map[source]), ) ) field_names = tuple(source_map[f][0] for f in unique_together) validator = UniqueTogetherValidator( queryset=parent_class._default_manager, fields=field_names ) validators.append(validator) return validators def get_unique_for_date_validators(self): «»» Determine a default set of validators for the following constraints: * unique_for_date * unique_for_month * unique_for_year «»» info = model_meta.get_field_info(self.Meta.model) default_manager = self.Meta.model._default_manager field_names = [field.source for field in self.fields.values()] validators = [] for field_name, field in info.fields_and_pk.items(): if field.unique_for_date and field_name in field_names: validator = UniqueForDateValidator( queryset=default_manager, field=field_name, date_field=field.unique_for_date ) validators.append(validator) if field.unique_for_month and field_name in field_names: validator = UniqueForMonthValidator( queryset=default_manager, field=field_name, date_field=field.unique_for_month ) validators.append(validator) if field.unique_for_year and field_name in field_names: validator = UniqueForYearValidator( queryset=default_manager, field=field_name, date_field=field.unique_for_year ) validators.append(validator) return validators class HyperlinkedModelSerializer(ModelSerializer): «»» A type of `ModelSerializer` that uses hyperlinked relationships instead of primary key relationships. Specifically: * A ‘url’ field is included instead of the ‘id’ field. * Relationships to other instances are hyperlinks, instead of primary keys. «»» serializer_related_field = HyperlinkedRelatedField def get_default_field_names(self, declared_fields, model_info): «»» Return the default list of field names that will be used if the `Meta.fields` option is not specified. «»» return ( [self.url_field_name] + list(declared_fields) + list(model_info.fields) + list(model_info.forward_relations) ) def build_nested_field(self, field_name, relation_info, nested_depth): «»» Create nested fields for forward and reverse relationships. «»» class NestedSerializer(HyperlinkedModelSerializer): class Meta: model = relation_info.related_model depth = nested_depth 1 fields = ‘__all__’ field_class = NestedSerializer field_kwargs = get_nested_relation_kwargs(relation_info) return field_class, field_kwargs

All Known Subinterfaces:
BindingResult
All Known Implementing Classes:
AbstractBindingResult, AbstractErrors, AbstractPropertyBindingResult, BeanPropertyBindingResult, BindException, DirectFieldBindingResult, EscapedErrors, MapBindingResult, MethodArgumentNotValidException, WebExchangeBindException

public interface Errors

Stores and exposes information about data-binding and validation
errors for a specific object.

Field names can be properties of the target object (e.g. «name»
when binding to a customer object), or nested fields in case of
subobjects (e.g. «address.street»). Supports subtree navigation
via setNestedPath(String): for example, an
AddressValidator validates «address», not being aware
that this is a subobject of customer.

Note: Errors objects are single-threaded.

Author:
Rod Johnson, Juergen Hoeller
See Also:
  • setNestedPath(java.lang.String)
  • BindException
  • DataBinder
  • ValidationUtils
  • Field Summary

    Fields

    The separator between path elements in a nested path,
    for example in «customer.name» or «customer.address.street».

  • Method Summary

    void

    Add all errors from the given Errors instance to this
    Errors instance.

    Get all errors, both global and field ones.

    int

    Return the total number of errors.

    Get the first error associated with a field, if any.

    Get the first error associated with the given field, if any.

    int

    Return the number of errors associated with a field.

    int

    Return the number of errors associated with the given field.

    Get all errors associated with a field.

    Get all errors associated with the given field.

    Return the type of a given field.

    Return the current value of the given field, either the current
    bean property value or a rejected update from the last binding.

    Get the first global error, if any.

    int

    Return the number of global errors.

    Return the current nested path of this Errors object.

    Return the name of the bound root object.

    boolean

    hasErrors()

    Return if there were any errors.

    boolean

    Are there any field errors?

    boolean

    Are there any errors associated with the given field?

    boolean

    Are there any global errors?

    void

    Pop the former nested path from the nested path stack.

    void

    Push the given sub path onto the nested path stack.

    void

    Register a global error for the entire target object,
    using the given error description.

    void

    Register a global error for the entire target object,
    using the given error description.

    void

    Register a global error for the entire target object,
    using the given error description.

    void

    Register a field error for the specified field of the current object
    (respecting the current nested path, if any), using the given error
    description.

    void

    Register a field error for the specified field of the current object
    (respecting the current nested path, if any), using the given error
    description.

    void

    Register a field error for the specified field of the current object
    (respecting the current nested path, if any), using the given error
    description.

    void

    Allow context to be changed so that standard validators can validate
    subtrees.

  • Field Details

    • NESTED_PATH_SEPARATOR

      static final String NESTED_PATH_SEPARATOR

      The separator between path elements in a nested path,
      for example in «customer.name» or «customer.address.street».

      «.» = same as the
      nested property separator
      in the beans package.

      See Also:
      • Constant Field Values
  • Method Details

    • getObjectName

      Return the name of the bound root object.

    • setNestedPath

      void setNestedPath(String nestedPath)

      Allow context to be changed so that standard validators can validate
      subtrees. Reject calls prepend the given path to the field names.

      For example, an address validator could validate the subobject
      «address» of a customer object.

      Parameters:
      nestedPath — nested path within this object,
      e.g. «address» (defaults to «», null is also acceptable).
      Can end with a dot: both «address» and «address.» are valid.
    • getNestedPath

      Return the current nested path of this Errors object.

      Returns a nested path with a dot, i.e. «address.», for easy
      building of concatenated paths. Default is an empty String.

    • pushNestedPath

      void pushNestedPath(String subPath)

      Push the given sub path onto the nested path stack.

      A popNestedPath() call will reset the original
      nested path before the corresponding
      pushNestedPath(String) call.

      Using the nested path stack allows to set temporary nested paths
      for subobjects without having to worry about a temporary path holder.

      For example: current path «spouse.», pushNestedPath(«child») →
      result path «spouse.child.»; popNestedPath() → «spouse.» again.

      Parameters:
      subPath — the sub path to push onto the nested path stack
      See Also:
      • popNestedPath()
    • popNestedPath

      Pop the former nested path from the nested path stack.

      Throws:
      IllegalStateException — if there is no former nested path on the stack
      See Also:
      • pushNestedPath(java.lang.String)
    • reject

      void reject(String errorCode)

      Register a global error for the entire target object,
      using the given error description.

      Parameters:
      errorCode — error code, interpretable as a message key
    • reject

      void reject(String errorCode,
      String defaultMessage)

      Register a global error for the entire target object,
      using the given error description.

      Parameters:
      errorCode — error code, interpretable as a message key
      defaultMessage — fallback default message
    • reject

      Register a global error for the entire target object,
      using the given error description.

      Parameters:
      errorCode — error code, interpretable as a message key
      errorArgs — error arguments, for argument binding via MessageFormat
      (can be null)
      defaultMessage — fallback default message
    • rejectValue

      Register a field error for the specified field of the current object
      (respecting the current nested path, if any), using the given error
      description.

      The field name may be null or empty String to indicate
      the current object itself rather than a field of it. This may result
      in a corresponding field error within the nested object graph or a
      global error if the current object is the top object.

      Parameters:
      field — the field name (may be null or empty String)
      errorCode — error code, interpretable as a message key
      See Also:
      • getNestedPath()
    • rejectValue

      Register a field error for the specified field of the current object
      (respecting the current nested path, if any), using the given error
      description.

      The field name may be null or empty String to indicate
      the current object itself rather than a field of it. This may result
      in a corresponding field error within the nested object graph or a
      global error if the current object is the top object.

      Parameters:
      field — the field name (may be null or empty String)
      errorCode — error code, interpretable as a message key
      defaultMessage — fallback default message
      See Also:
      • getNestedPath()
    • rejectValue

      Register a field error for the specified field of the current object
      (respecting the current nested path, if any), using the given error
      description.

      The field name may be null or empty String to indicate
      the current object itself rather than a field of it. This may result
      in a corresponding field error within the nested object graph or a
      global error if the current object is the top object.

      Parameters:
      field — the field name (may be null or empty String)
      errorCode — error code, interpretable as a message key
      errorArgs — error arguments, for argument binding via MessageFormat
      (can be null)
      defaultMessage — fallback default message
      See Also:
      • getNestedPath()
    • addAllErrors

      void addAllErrors(Errors errors)

      Add all errors from the given Errors instance to this
      Errors instance.

      This is a convenience method to avoid repeated reject(..)
      calls for merging an Errors instance into another
      Errors instance.

      Note that the passed-in Errors instance is supposed
      to refer to the same target object, or at least contain compatible errors
      that apply to the target object of this Errors instance.

      Parameters:
      errors — the Errors instance to merge in
    • hasErrors

      boolean hasErrors()

      Return if there were any errors.

    • getErrorCount

      int getErrorCount()

      Return the total number of errors.

    • getAllErrors

      Get all errors, both global and field ones.

      Returns:
      a list of ObjectError instances
    • hasGlobalErrors

      boolean hasGlobalErrors()

      Are there any global errors?

      Returns:
      true if there are any global errors
      See Also:
      • hasFieldErrors()
    • getGlobalErrorCount

      int getGlobalErrorCount()

      Return the number of global errors.

      Returns:
      the number of global errors
      See Also:
      • getFieldErrorCount()
    • getGlobalErrors

      Get all global errors.

      Returns:
      a list of ObjectError instances
    • getGlobalError

      Get the first global error, if any.

      Returns:
      the global error, or null
    • hasFieldErrors

      boolean hasFieldErrors()

      Are there any field errors?

      Returns:
      true if there are any errors associated with a field
      See Also:
      • hasGlobalErrors()
    • getFieldErrorCount

      int getFieldErrorCount()

      Return the number of errors associated with a field.

      Returns:
      the number of errors associated with a field
      See Also:
      • getGlobalErrorCount()
    • getFieldErrors

      Get all errors associated with a field.

      Returns:
      a List of FieldError instances
    • getFieldError

      Get the first error associated with a field, if any.

      Returns:
      the field-specific error, or null
    • hasFieldErrors

      boolean hasFieldErrors(String field)

      Are there any errors associated with the given field?

      Parameters:
      field — the field name
      Returns:
      true if there were any errors associated with the given field
    • getFieldErrorCount

      int getFieldErrorCount(String field)

      Return the number of errors associated with the given field.

      Parameters:
      field — the field name
      Returns:
      the number of errors associated with the given field
    • getFieldErrors

      Get all errors associated with the given field.

      Implementations should support not only full field names like
      «name» but also pattern matches like «na*» or «address.*».

      Parameters:
      field — the field name
      Returns:
      a List of FieldError instances
    • getFieldError

      Get the first error associated with the given field, if any.

      Parameters:
      field — the field name
      Returns:
      the field-specific error, or null
    • getFieldValue

      Return the current value of the given field, either the current
      bean property value or a rejected update from the last binding.

      Allows for convenient access to user-specified field values,
      even if there were type mismatches.

      Parameters:
      field — the field name
      Returns:
      the current value of the given field
    • getFieldType

      Return the type of a given field.

      Implementations should be able to determine the type even
      when the field value is null, for example from some
      associated descriptor.

      Parameters:
      field — the field name
      Returns:
      the type of the field, or null if not determinable

  • get_fields is suddenly returning a null value after today’s update. No other changes were made to the site. I took over development from someone else, so this isn’t my code. Was this function depreciated or changed? After switching to get_post_fields, the expected values are returned.

  • Can you please check the code is get_field and not get_fields (note the s)

  • Same issue here. After updating to version 5.11, get_field returns null on my taxonomies:

    get_field('my_field', $taxonomy . '_' . $term_id)

  • What happens if you toggle the return settings and resave, does that fix it?

    So if it’s set to return an array, switch to ID, save, switch back and resave?

  • My bad, no “s”, should have copied paste that. Here was the code that returned null on a key being a taxonomy image slug:

    $img = get_field($key);

    Switched to this as a quick fix

    $img = get_post_field( $key );

  • I’m seeing the same thing after the update.

    All get_field/the_field functions return null.

    I’m seeing this on multiple sites.

    The fields aren’t added by the user or registered in ACF, they are imported into the post from the contents of an API.

    Has there been a change where get_field will only return info if the field is registered by acf or data explicitly entered by the user?

  • Hey folks,

    This is likely happening as a result of a recent security update to the logic behind get_field() and similar functions.

    The problem was that before ACF 5.11, get_field() would return a value even if there wasn’t a matching field. This meant that it could be used to grab non-ACF data, including arbitrary options or user meta.

    However, get_field() should still work in ACF 5.11, as long as the field that you want the value for exists.

    We have seen some cases in support where fields/field groups registered via PHP (via acf_add_local_field_group(), acf_add_local_field(), and the like) are registered on a late action hook, or on a hook specific to the admin or front end, etc.

    In those cases, though the field is registered, it may not available at the time that get_field() is called, so null is returned.

    If you think that might be what’s happening to you, registering the fields on the “acf/init” action hook or just in your theme’s functions.php file should resolve the issue. Otherwise, feel free to reply here or shoot us an email so we can dig into it further.

  • In our project, those fields were not registered via PHP. They were created using the ACF WP Editor.

  • I’m going to update mine to use get_post_meta() instead as the fields in the API’s can change.
    https://developer.wordpress.org/reference/functions/get_post_meta/

    For anyone wanting to do the same, remember that if you just want a single value, you have to set the last parameter to true.
    Otherwise, even if there’s only one field you will still get an array returned containing one item with the value you want.

    @retroriff did yo try toggling the return options? Maybe also try changing the data in the field you are trying to get and save it as well.

  • The following code now returns nothing on the category.php template, used to work fine. I use this code on multiple sites:

    <php while (have_posts()) : the_post(); ?>
    <h3 class="text-center"><?php the_title(); ?></h3/>
    <?php the_field('r4m_video_url'); ?>
    <?php if ( get_field( 'r4m_video_description') ): ?>
    <?php the_field('r4m_video_description'); ?>
    <?php endif; ?>
    <?php endwhile; ?>

    ‘r4m_video_url’ is a text field for iframe code.

    Any ideas what is causing the error?

  • @aodesign have you tried following @jarvis suggestion above?

  • @robwent do you mean switch between the return array options, by editing the field group? Did it work for you? Text field doesn’t have different return formats, or perhaps I’m misunderstanding?

  • @aodesign No, you are correct.
    I would try resaving the field in a post and see if that puts it back, or try specifying the post ID as a second parameter just in case.

    If not, you can download the last version from the downloads page for a temporary fix if it’s a live site.

    My issue was that the postmeta isn’t registered as an acf field but I was using get_field to get the information anyway.

  • @robwent thanks for your input. The field works in a post, but not in the category. I’ve rolled back the ACF plugin for the time being. At least my site is back up again.

  • This update made ACF completely unusable on my website. Rolling back the version this evening.

  • As mentioned by @mattshaw if you are using ACF to manage fields created by some other plugin or value then it is usually a bad idea to do so without creating an ACF field to replace the other field and then hide the other field so that it cannot be used.

    As an example, on every site I built I replace the built in WP featured image field. I do this so that I can require a feature image because the front end design depends on the featured image existing. I create a field with the label “Featured Image” and the name “_thumbnail_id” which is identical to the meta key used by WP for holding the featured image ID. Then I hide the standard featured image input field. This makes it so that I can use get_field('_thumbnail_id') and also makes the value available to all standard WP functions that deals with the featured image.

    I can understand how this can lead to problems for some but the developers of ACF are enforcing what would be considered best practices. get_field() should not be used for getting meta values for fields created outside of ACF. get_{$object_type}_meta() should have been using all along in these cases.

    Another reason of not using get_field() for non ACF fields is simply readability and needing to figure out code in the future. If I see get_field('field name') I and other developers will automatically think that this is an ACF field and I will start looking for this field in the field groups if I need to change something. I will be completely confused when I don’t find that field. It will make the code much harder for others to work on or even myself a year after I’ve written it.

    Another thing to consider is that ACF fields should always be defined no matter how/when they will be used and other methods of showing and hiding those fields should be used rather than conditionally creating the fields. This is one of the uses for the acf/prepare_field filter which can be used to conditionally include a field when showing a field group.

  • @hube2 It may be best practice, but this is also a breaking change that came without warning.

    I’m not really clear on how this improves security, but it would have been a good idea to give a heads up considering how many sites are set to auto-update.

  • Here is a possible fix for those using get_field() for non ACF fields. This is untested. Most of it is pulled out of the function acf_get_metadata().

    
    add_filter('acf/load_value', 'get_meta_or_option_for_acf_get_field', 20, 3);
    function get_post_meta_for_acf_get_field($value, $post_id, $field) {
      if ($value !== NULL) {
        return $value;
      }
      $decoded = acf_decode_post_id( $post_id );
      $id      = $decoded['id'];
      $type    = $decoded['type'];
      if (!$id) {
        return $value;
      }
      $name = $field['name'];
      if ($type === 'option') {
        return get_option("{$id}_{$name}", NULL);
      } else {
        $meta = get_metadata($type, $id, $name, false);
        return isset($meta[0]) ? $meta[0] : NULL;
      }
    }
    
  • @t0su @retroriff @aodesign @williamsp

    If you’re still having issues, could you please shoot an email to [email protected] so we can troubleshoot further? We’re happy to help out, although it might be a bit tough on the forum.

  • @robwent I definitely see where you’re coming from. I think part of the issue is that we underestimated the number of folks that are using get_field() to get values for fields that don’t technically exist or to get fields unrelated to ACF.

    The other part of the issue is that this was part of a security fix, which we would typically prefer to hold off on disclosing until folks have had a chance to update. That said, we definitely could have done better at documenting this once we did release the update. We’ve since updated the docs for get_field() to include some notes about this change, and updated the release post to point to those notes.

    Regarding how this change improves security – the problem with get_field() being able to get arbitrary values is that it opens up vulnerabilities with things like AJAX requests or form submissions where get_field() might be manipulated by the request data.

  • We are seeing this issue as well.

    We run quite a few large multi-sites and after using switch_to_blog() the get_field(‘something’, ‘options’) returns null.

    This is a big issue for us.

    We found a temp fix, but this seems like a breaking change that needs to be rolled back.

    Joe

  • @floodlightdesign I believe that the only reason you did not have issues with this is the past is that you were using basic fields or possibly other types of fields that store simple text values. That also goes for getting fields with ACF when not created in ACF. The problem on multisite is that the fields need to be defined on the site you are switching from. There have been many topics over the years about this on multisite and ways to work around it. How you’d do this would be dependent on what you’re doing. If you post a new topic with the details I might be able to help you correct.

    I’m not part of the ACF dev team, but I do support what they have implemented. However, I do understand how this can be a problem for those that have depended on what was basically an unintended feature and that it could mean rebuilding sites. That’s why I posted the code that I posted above. While I don’t have this issue, if I did I’d want to be able to update ACF and get site back up and running as quickly as possible until I could fix correctly (or not).

    I will add that the code I provided will only work for fields that store simple text values in the DB because when the ACF field is not defined then ACF will be unable to format more complex fields correctly. But again, I’m assuming that most of the issue has to do with text based field since this flaw in ACF would never have worked with complex data types in the first place.

  • Appreciate the response – we are well aware of a fix. The fix on hundreds of sites becomes less straightforward.

    The main concern is this should not have happened in the first place.

    ACF Pro has been a solid part of our enterprise website implementation strategy for many years. I understand that issues happen, but this seems to be the first of its kind that has effected us so extensively. Changes that break or eliminate previous functionality need to be well publicized and documented.

  • Luckily for me, I only had 1 site affected and only 4 places in the code that needed updating to accommodate the new security feature. Being in a full-time cybersecurity role, I have a lot of appreciation for this feature so that it doesn’t bleed over from unintended functionality. However, more disclosure/documentation about this update and how to mitigate would have been nice for developers so that our sites don’t suddenly go down after auto-updating (as it was for mine) and then scrambling to figure out what caused it.

    I’ve never used this particular plugin in combination with custom developments, so seeing the format of the function name I originally assumed it was a built-in function to WordPress. Took me a little while to figure out no built-in function existed and this was part of ACF. Perhaps adding the plugin tag infront of the function, such as acf_get_field, could have helped ID this as a plugin issue sooner. I appreciate all the support this forum has provided, which will have me considering using this more often for future projects.

  • It’s totally NOT professional.

    Does the new developer REALLY forgot that ACF supports loading local fields from a JSON file, i.e. the fields definition will NOT exist per se, but will exist in a json that ACF loads.

    Local JSON

    In my case all my :

    get_field(‘blabla_option_definition_that_should_be_loaded_from_json’, ‘option’)

    are now returning NULL instead of something, breaking all logic based on that.

    In case the new developer didn’t know that too : ACF is also used to generate Options page that have sitewide effects: https://www.advancedcustomfields.com/resources/options-page/

    As a result I have stored ACF field definitions in a JSON file that is totally ignored and thus all sitewide settings stored are ignored.

    And this has been reported on the 10th but you let everyone discover your error, losing money for 3 days wondering what was going on…

    What the hell are you waiting for AT LEAST inform every pro users that there is a glitch and that they should check everything is working fine???

    Very very upset to see this plugin has been acquired and how it’s now being developed.

  • Viewing 25 posts — 1 through 25 (of 88 total)

    Понравилась статья? Поделить с друзьями:
  • Get error reporting php
  • Get error message php
  • Get error end of file resetting in 5 seconds bminer
  • Get error codes
  • Get error code java