Pregunta

He golpeado un pequeño dilema! Tengo un controlador denominado voto; cuando se invoca que establece el voto de un usuario a lo que han recogido. Para recordar lo que las opciones que escogieron previamente, almaceno Opciones VoteRecord el que detalla lo que su voto actual se establece.

Por supuesto, la primera vez que votan, tengo que crear el objeto y almacenarlo. Pero votaciones sucesivas sólo debe cambiar el valor de la VoteRecord existente. Pero viene el problema: en algunas circunstancias, dos VoteRecords se pueden crear. Es raro (sólo ocurrió una vez en todos los 500 votos que hemos visto hasta ahora), pero sigue siendo malo cuando lo hace.

El problema ocurre porque dos manejadores diferentes ambos hacen esencialmente lo siguiente:

query = VoteRecord.all().filter('user =', session.user).filter('poll =', poll)

if query.count(1) > 0:
 vote = query[0]

 poll.votes[vote.option] -= 1
 poll.votes[option] += 1
 poll.put()

 vote.option = option
 vote.updated = datetime.now()
 vote.put()
else:
 vote = VoteRecord()
 vote.user = session.user
 vote.poll = poll
 vote.option = option
 vote.put()

 poll.votes[option] += 1
 poll.put()

 session.user.votes += 1
 session.user.xp += 3
 session.user.put()

 incr('votes')

Mi pregunta es: ¿cuál es el más eficaz y más rápida de manejar estas solicitudes al tiempo que garantiza que ninguna solicitud se pierde y no hay petición crea dos objetos VoteRecord

¿Fue útil?

Solución

La cuestión es esta parte:

if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user
    obj.option = option
    obj.put()

Sin una transacción, el código podría funcionar en este orden en dos casos de interpretación:

if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user


if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user
    obj.option = option
    obj.put()


    obj.option = option
    obj.put()

o cualquier combinación de los mismos raro. El problema es la prueba de recuento se ejecuta de nuevo antes de que la venta ha ocurrido, por lo que el segundo hilo pasa a través de la primera parte del condicional en lugar del segundo.

Puede solucionar este problema al poner el código en una función y luego utilizando

db.run_in_transaction()

para ejecutar la función.

El problema es que parece estar confiando en el recuento de los objetos devueltos por una consulta para su lógica de decisión que debe ser puesto en la transacción. Si usted lee las conversaciones de Google I / O o mirar el grupo verá que esto no es recomendable. Eso es porque no se puede transactionalize una consulta. En su lugar, debe almacenar la cuenta como un valor de la entidad en alguna parte, la consulta para que fuera de la función de transacción, y luego pasar la llave para esa entidad a su función de transacción.

Aquí está un ejemplo de una función de transacción que comprueba una propiedad de entidad. Se transmite la clave como parámetro:

def checkAndLockPage(pageKey):
  page = db.get(pageKey)
  if page.locked:
    return False
  else:
    page.locked = True
    page.put()
    return True

Sólo un usuario a la vez puede bloquear esta entidad, y nunca habrá ningún bloqueo duplicadas.

Otros consejos

La forma más sencilla de hacerlo es utilizar nombres clave para su voto de objetos, y utilizar Model.get_or_insert. En primer lugar, llegar a un esquema de nomenclatura para los nombres de claves - dándole el nombre de la encuesta es una buena idea - y luego hacer un get_or_insert a buscar o crear la entidad correspondiente:

vote = VoteRecord.get_or_insert(pollname, parent=session.user, user=session.user, poll=poll, option=option)
if vote.option != option:
  # Record already existed; we need to update it
  vote.option = option
  vote.put()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top