О терминологии веб-фреймворка Django
- Проект - совокупность приложений, имеющих общие настройки.
- Приложение - часть проекта, выполняющая определённую логически неделимую функцию. Состоит из представлений (views), шаблонов (templates) и моделей (models).
- Шаблон - шаблон HTML-страницы. В терминологии MVC шаблону соответствует представление.
- Модель - средство доступа к данным.
- Представление - связующий код между моделью и шаблоном. В терминологии MVC представлению соответствует контроллеру.
- Маршрут - соответствие между URL'ом и представлением (контроллером в терминологии MVC), отвечающим за этот URL.
Установка Python и Django
В Debian и производных от него дистрибутивах установить Python и Django можно из репозиториев:# apt-get install python python-django
Создание проекта и приложения
Создаём проект dj:$ django-admin startproject djПереходим в каталог проекта:
$ cd djСмотрим содержимое каталога проекта:
$ find . . ./manage.py ./dj ./dj/wsgi.py ./dj/settings.py ./dj/__init__.py ./dj/urls.pyСоздаём в проекте новое приложение app:
$ ./manage.py startapp appВ каталоге проекта появится новый каталог с именем app, созданный специально для размещения приложения:
$ find . . ./manage.py ./dj ./dj/wsgi.py ./dj/settings.py ./dj/__init__.py ./dj/urls.py ./dj/settings.pyc ./dj/__init__.pyc ./app ./app/tests.py ./app/views.py ./app/models.py ./app/__init__.py
Создание представления
Создадим новое представление hello в приложении app. Представление принимает в качестве аргумента объект HttpRequest и возвращает объект HttpResponse.Откроем для редактирования файл app/views.py и придадим ему следующий вид:
# -*- coding: UTF-8 -*- from django.http import HttpResponse def hello(request): return HttpResponse(u'Здравствуй, мир!')
Создание маршрута
Теперь настроим маршрут, вызывающий это представление для url. Для этого откроем файл dj/urls.py и добавим в него пару строчек.Первую строчку добавим в начало файла, после других строчек импорта. Строчка импортирует представление hello из приложения app:
from app.views import helloТеперь найдём функцию patterns, возвращаемое значение которой присваивается переменной urlpatterns, и впишем в аргументы функции следующую строчку:
('^hello/$', hello),В итоге у меня содержимое файла dj/urls.py приняло следующий вид:
from django.conf.urls import patterns, include, url from app.views import hello # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'dj.views.home', name='home'), # url(r'^dj/', include('dj.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # url(r'^admin/', include(admin.site.urls)), ('^hello/$', hello), )В шаблоне url в конце обязательно должна быть косая черта, т.к. если клиент запросит страницу без косой черты в конце, Django автоматически добавит её и попытается найти представление, соответствующее этому URL. Нужно ли добавлять косую черту, регулируется настройкой APPEND_SLASH. Если установить её в False, то косая черта добавляться не будет. Шаблоном для корня сайта является '^$', то есть соответствие пустой строке.
Включение приложения в проекте
Чтобы в проекте использовалось наше приложение, его нужно подключить к проекту. Для этого закомментируем на время список стандартных приложений в файле dj/settings.py и пропишем наше приложение:INSTALLED_APPS = ( #'django.contrib.auth', #'django.contrib.contenttypes', #'django.contrib.sessions', #'django.contrib.sites', #'django.contrib.messages', #'django.contrib.staticfiles', # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'app', )Также закомментируем в настройках подключение приложений-прослоек:
MIDDLEWARE_CLASSES = ( #'django.middleware.common.CommonMiddleware', #'django.contrib.sessions.middleware.SessionMiddleware', #'django.middleware.csrf.CsrfViewMiddleware', #'django.contrib.auth.middleware.AuthenticationMiddleware', #'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', )
Запуск сервера разработчика
Теперь настало время запустить сервер разработчика с только что созданным простейшим приложением:$ ./manage.py runserver 0.0.0.0:8000Можно попытаться зайти в браузере на страницу /hello/. У меня ссылка для открытия страницы выглядела так:
http://localhost:8000/hello/
Итак, мы создали наш первый проект на Django, который при обращении к странице /hello/ выводит надпись "Здравствуй, мир!" Возможности Django в этом проекте практически не используются - мы не использовали ни моделей, ни шаблонов, но этот проект даёт общее представление о структуре программ, написанных с использованием Django.
Использование шаблонов
Для начала настроим каталог, в котором будут находиться шаблоны. Для этого отредактируем файл dj/settings.py и впишем в кортеж TEMPLATES_DIRS полный путь к каталогу с шаблонами.После редактирования файла эта настройка в файле settings.py приняла следующий вид:
TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. "/home/stupin/dj/templates", )Не забывайте в конце одноэлементного кортежа поставить запятую, чтобы Python мог отличить кортеж от простого выражения в скобках.
Теперь создадим каталог для шаблонов:
$ mkdir templatesИ создадим в нём новый шаблон с именем time.tmpl и со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Текущее время</title> </head> <body> <div align="center"> Сейчас {{ time }} </div> </body> </html>Теперь добавим в файл app/views.py импорт функции для загрузки и отрисовки шаблона:
from django.shortcuts import render_to_responseИ создадим представление current_datetime следующим образом:
def current_datetime(request): return render_to_response('time.tmpl', {'time' : datetime.now()})Осталось настроить в файле urls.py маршрут к этому представлению:
('^time/$', current_datetime),Если сейчас попробовать открыть страницу http://localhost:800/time/, то можно увидеть текущее время на английском языке в часовом поясе Гринвичской обсерватории.
Настройка часового пояса и языка
Чтобы время отображалось правильно - в часовом поясе сервера, пропишем в файл настроек местный часовой пояс. Для этого откроем файл dj/settings.py и пропишем в переменную TIME_ZONE значение 'Asia/Yekaterinburg':TIME_ZONE = 'Asia/Yekaterinburg'Чтобы время на странице отображалось в соответствии с правилами, принятыми в России, пропишем в файл настроек язык проекта. Откроем файл dj/settings.py и пропишем в переменную LANGUAGE_CODE значение 'ru-RU':
LANGUAGE_CODE = 'ru-RU'Теперь текущее время должно отображаться на русском языке в часовом поясе, по которому живёт Уфа :)
Пример более сложного маршрута
Попробуем добавить страницы, которые будут вычитать из текущего времени часы, фигурирующие в URL запрошенной страницы. Для этого в файл dj/urls.py добавим маршруты:(r'^time/plus/(\d{1,2})$', hours_plus), (r'^time/minus/(\d{1,2})$', hours_minus),В каталоге с шаблонами templates разместим два новых шаблона.
Файл templates/time_minus.tmpl со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Прошлое время</title> </head> <body> <div align="center"> {{ delta }} часов назад было {{ time }} </div> </body> </html>Файл templates/time_plus.tmpl со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Будущее время</title> </head> <body> <div align="center"> Через {{ delta }} часов будет {{ time }} </div> </body> </html>В файл app/views.py пропишем два представления, которые будут использовать два новых шаблона:
def hours_plus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() + timedelta(hours=delta) return render_to_response('time_plus.tmpl', {'delta' : delta, 'time' : time}) def hours_minus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() - timedelta(hours=delta) return render_to_response('time_minus.tmpl', {'delta' : delta, 'time' : time})Файл app/views.py целиком примет следующий вид:
# -*- coding: UTF-8 -*- from django.http import HttpResponse, Http404 from django.shortcuts import render_to_response from datetime import datetime, timedelta def hello(request): return HttpResponse(u'Здравствуй, мир!') def current_datetime(request): return render_to_response('time.tmpl', {'time' : datetime.now()}) def hours_plus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() + timedelta(hours=delta) return render_to_response('time_plus.tmpl', {'delta' : delta, 'time' : time}) def hours_minus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() - timedelta(hours=delta) return render_to_response('time_minus.tmpl', {'delta' : delta, 'time' : time})Теперь можно перейти по ссылкам http://localhost:8000/time/plus/1 или http://localhost:8000/time/plus/2 и увидеть получающиеся страницы.
Более сложные шаблоны
Условие:{% if today_is_weekend %} <p>Сегодня выходной!</p> {% endif %}Условие с двумя вариантами:
{% if today_is_weekend %} <p>Сегодня выходной!</p> {% else %} <p>Пора работать.</p> {% endif %}Ложным значениями являются: пустой список [], пустой кортеж (), пустой словарь {}, ноль - 0, объект None и объект False.
Можно использовать сочетания условий при помощи and и or, причём and имеет более высокий приоритет. Скобки в условиях не поддерживаются, без них можно обойтись с помощью вложенных условий. Также возможно использовать операторы ==, !=, <, >, >=, <= и in для вычисления условий, по смыслу совпадающих с условными операторами в самом Python. Циклы для перебора значений из списка:
{% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %}Можно перебирать значения из списка в обратном порядке:
{% for athlete in athlete_list reversed %} ... {% endfor %}Внутри циклов существуют следующие переменные:
- {{ forloop.counter }} - номер итерации, начиная с 1,
- {{ forloop.counter0 }} - номер итерации, начиная с 0,
- {{ forloop.revcounter }} - количество оставшихся итераций, включая текущую. Внутри первой итерации равно количеству элементов, на последней итерации - 1,
- {{ forloop.revcounter }} - количество оставшихся итераций. Внутри первой итерации содержит количество элементов за минус единицей, на последней итерации - 0,
- {% if forloop.first %} - условие выполняется на первой итерации,
- {% if forloop.last %} - условие выполняется на последней итерации,
- forloop.parentloop - ссылка на объект родительского цикла.
{# Это комментарий #}или
{% comment %} Многострочный комментарий. {% endcomment %}
Включение подшаблонов
В шаблон можно включать другой шаблон, в качестве фрагмента. Таким образом можно повторно использовать одни и те же фрагменты в разных шаблонах, а также для удобства разбивать большие сложные шаблоны на небольшие фрагменты.Включение подшаблона из файла:
{% include "includes/nav.html" %}Включение подшаблона из переменной:
{% include template_name %}
Наследование шаблонов
Наследование шаблонов - очень удобная концепция, которая встречается не во всех шаблонизаторах. В шаблонизаторе Django такая поддержка наследования шаблонов имеется. Наследование позволяет определить определить в базовом шаблоне общий дизайн для множества страниц. При этом в страницах-наследницах переопределяются отдельные блоки базовой страницы. В этих блоках будет выводиться содержимое, специфичное конкретно для этой страницы.Продолжим эксперименты с нашим тестовым проектом, добавив в него базовый шаблон.
Базовый шаблон base.tmpl:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>Бесполезный сайт с часами</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Благодарим за посещение нашего сайта.</p> {% endblock %} </body> </html>В базовом шаблоне помечаются блоки, в которых прописывается часть шаблона, специфичная для этой страницы.
Производный шаблон. Для примера приведу только содержимое шаблона time_plus.tmpl:
{% extends "base.tmpl" %} {% block title %}Будущее время{% endblock %} {% block content %} <p>Через {{ delta }} часов будет {{ time }}</p> {% endblock %}В производном шаблоне указывается базовый шаблон и переопределяется содержимое блоков базового шаблона. Шаблоны других страниц можно отредактировать сходным образом, чтобы и они использовали дизайн, общий для всех страниц.
Настройка базы данных MySQL
После того, как мы рассмотрели шаблоны Django, пришло время заняться моделями Django. Но прежде чем приступить непосредственно к изучению моделей, нужно установить необходимые модули, выставить настройки как в самой СУБД, так и в проекте Django.Установим модуль для доступа к базе данных MySQL из Python (разработчики фреймворка Django рекомендуют использовать PostgreSQL, но мы воспользуемся MySQL, поддержка которого тоже имеется):
# apt-get install python-mysqldbНастроим кодировку сервера MySQL и порядок сортировки. Для этого в файле /etc/mysql/my.cnf в секцию [mysqld] впишем следующие настройки:
character_set_server=utf8 collation_server=utf8_unicode_ciПерезапустим сервер базы данных:
# /etc/init.d/mysql restartВ результате вышеописанных действий должен получиться такой результат:
mysql> show variables like 'coll%'; +----------------------+-----------------+ | Variable_name | Value | +----------------------+-----------------+ | collation_connection | utf8_general_ci | | collation_database | utf8_unicode_ci | | collation_server | utf8_unicode_ci | +----------------------+-----------------+ 3 rows in set (0.00 sec) mysql> show variables like 'char%'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec)Создадим базу данных для проекта:
CREATE USER app@localhost IDENTIFIED BY "app_password"; CREATE DATABASE app; GRANT ALL ON app.* TO app@localhost;Пропишем в файл настроек проекта dj/settings.py настройки подключения к базе данных:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 'NAME': 'app', # Or path to database file if using sqlite3. 'USER': 'app', # Not used with sqlite3. 'PASSWORD': 'app_password', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } }Проверяем правильность настройки:
$ ./manage.py shellЕсли всё правильно, то откроется отладочная оболочка Python. Попробуем ввести следующие команды:
from django.db import connection cursor = connection.cursor()Если сообщений об ошибках не было, значит всё настроено правильно.
Создание моделей
Каждая модель Django описывают одну таблицу в базе данных. В Django имеется система отображения таблиц в объекты - ORM - Object-Relational Mapping - объектно-реляционное отображение. ORM позволяет манипулировать единичными строками таблиц как объектами Python, а также осуществлять операции массовой выборки, обновления и удаления строк.В качестве примера, рассмотрим адресный справочник домов в городах. Откроем файл app/models.py и приведём его к следующему виду:
# -*- coding: UTF-8 -*- from django.db import models class City(models.Model): country = models.ForeignKey(Country) title = models.CharField(max_length=150) class Street(models.Model): city = models.ForeignKey(City) title = models.CharField(max_length=150) class Area(models.Model): city = models.ForeignKey(City) title = models.CharField(max_length=150) class House(models.Model): area = models.ForeignKey(Area) street = models.ForeignKey(Street) house = models.IntegerField() frac = models.CharField(max_length=30) comment = models.CharField(max_length=100)Мы описали объекты, составляющие адресный справочник, и описали взаимоотношения между ними через внешние ключи - ForeignKey. В городе имеются улицы и районы, а каждый дом находится на одной улице и принадлежит одному из районов города.
В Django есть не только текстовые поля и внешние ключи. Имеются числовые поля, списковые поля, логические поля, поля для связей один-к-одному и для связей многие-ко-многим. У полей можно прописать значения по умолчанию, разрешить или запретить использовать значение NULL и запретить или разрешить вводить в строковые поля пустые строки. У поля можно прописать его имя в базе данных, выставить признак - нужно ли создавать индекс по этому полю, можно прописать текстовое описание поля, которое будет использоваться в веб-интерфейсе администратора и в объектах форм Django. У классов моделей, в свою очередь, можно тоже указывать их текстовые описания, прописывать составные индексы, ограничения уникальности записей и т.п. Создание моделей - это обширная тема, рассмотреть которую сколь-нибудь подробно в рамках этого небольшого учебника вряд ли получится.
Теперь мы можем проверить правильность синтаксиса и логики моделей:
$ ./manage.py validateЧтобы увидеть команды SQL для создания структуры базы данных, требуемой для моделей из приложения app, введём следующую команду:
$ ./manage.py sqlall appЧтобы выполнить эти операторы SQL и создать в базе данных таблицы, соответствующие моделям, нужно выполнить следующую команду:
$ ./manage.py syncdbМожно войти в базу данных клиентом и увидеть созданную структуру таблиц и их взаимосвязи.
Для входа в базу данных с настройками проекта, можно воспользоваться следующей командой:
$ ./manage.py dbshell
Создание записей в таблицах
Откроем оболочку Python:$ ./manage.py shellИмпортируем описание моделей:
from address.models import *Создадим объект "город":
c = City(title=u'Уфа')И сохраним его в базу данных:
c.save()Теперь создадим объект "улица" в этом городе:
s = Street(title=u'ул. Карла Маркса', city=c)И сохраним объект с улицей:
s.save()Если нужно отредактировать объект, то можно прописать в него новое свойство и сохранить:
c.title = u'г. Уфа' c.save()Недостаток такого рода редактирования объектов заключается в том, что в базе данных обновляются все поля в отредактированной строке таблицы, а не только то поле, которое действительно было изменено. Для раздельного редактирования полей строк можно воспользоваться массовым редактированием, о котором будет рассказано далее.
Извлечение записей из таблиц
Откроем оболочку Python:$ ./manage.py shellИмпортируем описание модели:
from address.models import *Загрузим все объекты типа "город":
c = City.objects.all()Загрузим объект "город", имеющий имя "г. Уфа":
c = City.objects.filter(title=u'г. Уфа')И убедимся, что загрузился именно он:
print cВыбор списка объектов:
- Obj.objects.all() - отобрать как список все объекты типа Obj,
- Obj.objects.filter(field='') - отобрать как список объекты, у которых поле field имеет указанное значение,
- Obj.objects.filter(field1='', field2='') - отобрать как список объекты, у которых оба поля одновременно имеют указанные значения,
- Obj.objects.filter(field__contains='') - отобрать как список объекты, у которых в указанном поле содержится указанное значение,
- Obj.objects.filter(field__icontains='') - отобрать как список объекты, у которых в указанном поле содержится указанное значение без учёта регистра,
- Obj.objects.filter(field__iexact='') - отобрать как список объекты, у которых указанное поле совпадает с указанным значением без учёта регистра,
- Obj.objects.filter(field__startswith='') - отобрать как список объекты, у которых указанное поле начинается с указанного значения,
- Obj.objects.filter(field__istartswith='') - отобрать как список объекты, у которых указанное поле начинается с указанного значения без учёта регистра,
- Obj.objects.filter(field__endswith='') - отобрать как список объекты, у которых указанное поле заканчивается указанным значением,
- Obj.objects.filter(field__iendswith='') - отобрать как список объекты, у которых указанное поле заканчивается указанным значением без учёта регистра.
Obj.objects.filter(field1__iendswith='a').filter(field2='b')Чтобы отобрать объекты, не подходящие под указанное условие, можно воспользоваться методом фильтрации exclude. Например, следующее выражение отберёт те записи, у которых начало первого поля без учёта регистра совпадает с a, а второе поле не равно b:
Obj.objects.filter(field1__iendswith='a').exclude(field2='')Выбор одного объекта осуществляется точно таким же способом, как выбор списка, за исключением того, что вместо метода filter используется метод get:
Obj.objects.get(field='')Если ни один объект не был найден, будет сгенерировано исключение Obj.DoesNotExist. Если указанным критериям соответствует несколько записей, то будет сгенерировано исключение Obj.MultipleObjectsReturned.
Как и в случае выборки списка объектов, можно комбинировать фильтры друг с другом. Но для выборки одного объекта последним методом в цепочке должен быть get. Например, вот так:
Obj.objects.filter(field1__iendswith='a').exclude(field2='').get()Или, что то же самое, вот так:
Obj.objects.exclude(field2='').get(field1__iendswith='a')В Django версий 1.6 и более поздних имеется метод first(), не принимающий аргументов, который возвращает первую запись из списка, если она есть. В противном случае возвращается None. Стоит учитывать, что этот метод никак не обрабатывает случаи, когда условию соответствует несколько записей сразу.
Сортировка данных:
- Obj.objects.order_by("field") - сортировка по одному полю,
- Obj.objects.order_by("-field") - сортировка по одному полю в обратном порядке,
- Obj.objects.order_by("field1", "field2") - сортировка по двум полям.
class Meta: ordering = ["field"]Можно комбинировать методы:
Obj.objects.filter(field="").order_by("-field")Можно выбирать необходимый фрагмент списка объектов. Например, вот этот запрос вернёт два первых объекта:
Obj.objects.filter(field="").order_by("-field")[0:2]Массовое обновление объектов осуществляется следующим образом:
Obj.objects.filter(id=52).update(field='')Запрос возвращает количество обновлённых строк таблицы.
Удалить один объект можно следующим образом:
o = Obj.objects.get(field='') o.delete()Удалить объекты массово можно так:
Obj.objects.filter(field='').delete()
Активация интерфейса администратора
Пожалуй самая приятная особенность фреймворка Django - это встроенный веб-интерфейс администратора, который позволяет манипулировать записями на уровне отдельных таблиц. В большинстве случаев веб-интерфейс администратора позволяет сэкономить время на реализации большого количество весьма однообразных функций. Даже если понадобится сделать нечто необычное, веб-интерфейс поддаётся очень глубокой и тонкой настройке через объекты административного интерфейса, объекты форм, дополнительные действия и т.д. И лишь в совсем редких случаях может понадобиться реализовывать собственные страницы для манипуляции объектами приложения.Для включения интерфейса администрирования нужно внести изменения в файл настроек dj/settings.py:
- Вписать в INSTALLED_APPS приложения django.contrib.admin, django.contrib.auth, django.contrib.sessions и django.contrib.contenttypes,
- Вписать в MIDDLEWARE_CLASSES приложения-прослойки django.middleware.common.CommonMiddleware, django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware и django.contrib.messages.middleware.MessageMiddleware.
$ ./manage.py syncdbЕсли при этом отказаться от создания суперпользователя, то потом его можно создать с помощью команды:
$ ./manage.py createsuperuserТеперь в начало файла dj/urls.py добавим использование модуля и его инициализацию:
from django.contrib import admin admin.autodiscover()И пропишем маршрут к интерфейсу администрирования:
urlpatterns = patterns('', # ... (r'^admin', include(admin.site.urls)), # ... )Для того, чтобы объекты можно было редактировать прямо из интерфейса администратора, нужно создать в каталоге приложения app файл admin.py со следующим содержимым:
from app.models import * from django.contrib import admin admin.site.register(City) admin.site.register(Area) admin.site.register(Street) admin.site.register(House)После этого можно перейти по ссылке http://localhost:8000/admin/, войти под учётными данными, указанными команде createsuperuser, и пользоваться интерфейсом администрирования для добавления, редактирования и удаления записей в таблицах.
Как уже было сказано, интерфейс администрирования поддаётся глубокой и тонкой настройке, но его настройка выходит за рамки этого учебника.
За рамками этого учебника также остались подробности описания моделей, выражения Q и F для конструирования более сложных запросов, не рассмотрены формы и модельные формы, не рассмотрена миграция структуры базы данных при изменении моделей и многое другое.
2 комментария:
А как можно массово обновить данные в таблице разными значениями. Вот например у таблицы Users у id=1 обновить имя, у id=2 обновить Фамилию, у id=3 обновить Отчество?
Массовое обновление предусматривает выполнение однообразных изменений над несколькими записями. Вы хотите не массовое обновление, а три отдельных.
Отправить комментарий