Я наткнулся на хитрую проблему с Джанго Queryset - PullRequest
1 голос
/ 30 ноября 2011

Хитрый код:

user = User.objects.filter(id=123)
user[0].last_name = 'foo'
user[0].save()  # Cannot be saved.
id(user[0])     # 32131
id(user[0])     # 44232 ( different )

Пользователь не может быть сохранен таким способом.

Обычный код:

user = User.objects.filter(id=123)
if user:
  user[0].last_name = 'foo'
  user[0].save()  # Saved successfully.
  id(user[0])     # 32131
  id(user[0])     # 32131 ( same )

Итак, в чем проблема?

Ответы [ 3 ]

5 голосов
/ 30 ноября 2011

В первом варианте ваш user набор запросов еще не оценен. Поэтому каждый раз, когда вы пишете user[0] ORM делает независимый запрос к БД. Во втором варианте набор запросов оценивается и действует как обычный список Python.

И, кстати, если вы хотите только одну строку, используйте get:

user = User.objects.get(id=123)
2 голосов
/ 30 ноября 2011

при индексации в наборе запросов django извлекает данные (или просматривает их кэш) и создает для вас экземпляр модели.как вы обнаружили с id(), каждый вызов создает новый экземпляр.поэтому, хотя вы можете установить свойства для этих qs[0].last_name = 'foo', последующий вызов qs[0].save() создает новый экземпляр (с исходным last_name) и сохраняет, что

Я предполагаю, что ваша конкретная проблема связана скогда django кэширует результаты запроса.когда вы просто индексируете в qs, ничего не кэшируется, но ваш вызов if users вызывает оценку всех (оригинальных) qs и, таким образом, кэширование.так что в этом случае каждый вызов [0] извлекает один и тот же экземпляр модели

1 голос
/ 30 ноября 2011

Сохранение возможно, но каждый раз, когда вы обращаетесь к пользователю [0], вы фактически получаете его из базы данных, поэтому он не изменяется.Действительно, когда вы нарезаете Queryset, Django отправляет запрос SELECT ... FROM ... OFFSET ... LIMIT ... в вашу базу данных.

Queryset - это не список, поэтому, если вы хотитеон должен вести себя как список, вам нужно оценить его, для этого вызвать list().

user = list(User.objects.filter(id=123))

Во втором примере вызов if user фактически оценит набор запросов (получите егоиз базы данных в вашу программу на python), поэтому вы затем работаете с внутренним кэшем Queryset.

В качестве альтернативы, вы можете использовать u = user[0], отредактировать его и затем сохранить, что будет работать.

Наконец, вы должны на самом деле звонить Queryset.get, а не filter здесь, так как вы используете уникальный ключ.

...