2011-11-15 14 views
21

En una vista django, necesito adjuntar datos de cadena al final de una columna de texto existente en mi base de datos. Entonces, por ejemplo, digamos que tengo una tabla llamada "ATable", y tiene un campo llamado "aField". Me gustaría poder agregar una cadena al final de "aField" en una forma libre de condiciones de carrera. Inicialmente, tenía esto:Django: Usar una expresión F para un campo de texto en una llamada de actualización

tableEntry = ATable.objects.get(id=100) 
tableEntry.aField += aStringVar 
tableEntry.save() 

El problema es que si esto se está ejecutando al mismo tiempo, ambos pueden obtener el mismo "tableEntry", a continuación, actualizar cada uno de ellos de forma independiente, y el último a "salvar" a gana, perdiendo los datos agregados por el otro.

miré en esto un poco y encontramos este, lo que yo esperaba que trabajar, utilizando una expresión F:

ATable.objects.filter(id=100).update(aField=F('aField') + aStringVar) 

El problema aquí, es que me da un error SQL, diciendo:

operator does not exist: text + unknown 
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. 

Intenté cambiar a "str (aStringVar)" aunque ya era una cadena, no tuve suerte ... Encontré un par de informes de errores de django quejándose de problemas similares, pero no vi una solución o una solución alternativa. ¿Hay alguna forma de que pueda lanzar un StringVar de forma que pueda adjuntarse al texto de la expresión F? Por cierto, también probé "str (F ('aField')) + aStringVar", pero eso convirtió el resultado de la expresión F en la cadena "(DEFAULT:)".

+0

¿Puedes ver el sqlquery que Django intenta ejecutar? – Willian

+1

posible duplicado de [¿Puedo usar objetos Django F() con concatenación de cadenas?] (Http://stackoverflow.com/questions/3300944/can-i-use-django-f-objects-with-string-concatenation) – Alasdair

+0

Creo que la explicación de Daniel sobre la otra pregunta es bastante definitiva. Como alternativa, puede escribir [sql personalizado] (https://docs.djangoproject.com/en/dev/topics/db/sql/#executing-custom-sql-directly) para realizar sus actualizaciones. – Alasdair

Respuesta

2

Parece que no puede hacer esto. Sin embargo, lo que está tratando de hacer podrían resolverse utilizando transactions

(parece que está utilizando Postgres, por lo que si desea hacerlo en una consulta y el uso de SQL en bruto como se sugiere, || es el operador de concatenación desea)

3

Y si lo ejecuta, no es seguro para subprocesos. Mientras se ejecuta su actualización, algunos otros procesos pueden actualizar un modelo sin saber que los datos en la base de datos se actualizan.

usted tiene demasiados obtener un bloqueo, pero no se olvide este senario:

  1. Django: m = Model.objects.all() [10]
  2. Django: m.field = campo
  3. Django: un progreso que lleva un tiempo (time.sleep (100))
  4. DB: mesa de bloqueo
  5. DB: campo Actualizar
  6. DD: Desbloquear tabla
  7. Django: el lento proceso está terminado
  8. Django: m.(Guardar)

Ahora la actualización de campo llegó a ser deshecho por la instancia de modelo de Django (Ghost escritura)

+0

Punto muy interesante (pero no realmente relacionado con la pregunta). ¿Tiene algún recurso para discutir cómo manejar este tipo de situaciones con Django? – lajarre

3

Puede conseguir esta funcionalidad con select_for_update de Django() operador: https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update

Algo como esto:

obj = ATable.objects.select_for_update().get(id=100) 
obj.aField = obj.aField + aStringVar 
obj.save() 

la fila de la tabla se bloquea cuando se llama .select_for_update(). get(), y el bloqueo se libera cuando se llama .save(), lo que permite realizar la operación atómica.

22

Puede anular F objeto en Django con un simple cambio:

class CF(F): 
    ADD = '||' 

Entonces sólo tiene que utilizar CF en lugar de F. Colocará "||" en lugar de "+" al generar SQL. Por ejemplo, la consulta:

User.objects.filter(pk=100).update(email=CF('username') + '@gmail.com') 

va a generar el código SQL:

UPDATE "auth_user" SET "email" = "auth_user"."username" || '@gmail.com' 
WHERE "auth_user"."id" = 100 
+1

Solo para completar esta respuesta (¡funcionó para mí! ¡Gracias!) También debe establecer 'sql_mode = 'PIPES_AS_CONCAT''; si no haces eso, mi MySQL considera "||" como lógica O –

+1

¡Buen punto! Estaba probando en PostgreSQL. De hecho, '||' no es un operador SQL estándar. La forma estándar es usar la función 'CONCAT()', pero no encaja en esta solución ... –

+0

¡Vea la respuesta a continuación para solución portátil usando Concat! –

15

Usted puede utilizar la función Concat db.

from django.db.models import Value 
from django.db.models.functions import Concat 

ATable.objects.filter(id=100).update(some_field=Concat('some_field', Value('more string'))) 

En mi caso, estoy añadiendo un sufijo para facebook avatares URI de la siguiente manera:

FACEBOOK_URI = 'graph.facebook.com' 
FACEBOOK_LARGE = '?type=large' 
# ... 
users = User.objects.filter(Q(avatar_uri__icontains=FACEBOOK_URI) & ~Q(avatar_uri__icontains=FACEBOOK_LARGE)) 
users.update(avatar_uri=Concat('avatar_uri', Value(FACEBOOK_LARGE))) 

y me sale SQL como esto (Django 1.9):

UPDATE `user_user` SET `avatar_uri` = CONCAT(COALESCE(`user_user`.`avatar_uri`, ''), COALESCE('?type=large', '')) 
WHERE (`user_user`.`avatar_uri` LIKE '%graph.facebook.com%' AND NOT (`user_user`.`avatar_uri` LIKE '%?type=large%' AND `user_user`.`avatar_uri` IS NOT NULL)) 

El resultado es todos los URIs de imagen cambiados de http://graph.facebook.com/<fb user id>/picture a http://graph.facebook.com/<fb user id>/picture?type=large

+1

Esta es la forma correcta de hacerlo desde Django 1.8 – Fush

Cuestiones relacionadas