2016-04-04 13 views
7

Ho tre modelli Giocatore, squadra e appartenenza, dove giocatore e squadra hanno molti-to- molte relazioni che utilizzano l'abbonamento come modello intermedio.Come aggiornare i valori in istanza quando si dispone di un custom.update() per aggiornare le relazioni molti-a-molti in serializzatore annidato scrivibile DRF

class Player(models.Model): 
    name = models.CharField(max_length=254) 
    rating = models.FloatField(null=True) 
    install_ts = models.DateTimeField(auto_now_add=True, blank=True) 
    update_ts = models.DateTimeField(auto_now_add=True, blank=True) 


class Team(models.Model): 
    name = models.CharField(max_length=254) 
    rating = models.FloatField(null=True) 
    players = models.ManyToManyField(
      Player, 
      through='Membership', 
      through_fields=('team', 'player')) 
    is_active = models.BooleanField(default=True) 
    install_ts = models.DateTimeField(auto_now_add=True, blank=True) 
    update_ts = models.DateTimeField(auto_now_add=True, blank=True) 


class Membership(models.Model): 
    team = models.ForeignKey('Team') 
    player = models.ForeignKey('Player') 
    #date_of_joining = models.DateTimeField() 
    install_ts = models.DateTimeField(auto_now_add=True, blank=True) 
    update_ts = models.DateTimeField(auto_now_add=True, blank=True) 

Ora mi è stato richiesto di aggiornare questo abbonamento utilizzando il framework django rest. Ho provato ad aggiornare quelli usando Writable nested serializers scrivendo un custom .update() di serializzatore di squadra.

@transaction.atomic 
def update(self, instance, validated_data): 
    ''' 
    Cutomize the update function for the serializer to update the 
    related_field values. 
    ''' 

    if 'memberships' in validated_data: 
     instance = self._update_membership(instance, validated_data) 

     # remove memberships key from validated_data to use update method of 
     # base serializer class to update model fields 
     validated_data.pop('memberships', None) 

    return super(TeamSerializer, self).update(instance, validated_data) 


def _update_membership(self, instance, validated_data): 
    ''' 
    Update membership data for a team. 
    ''' 
    memberships = self.initial_data.get('memberships') 
    if isinstance(membership, list) and len(memberships) >= 1: 
     # make a set of incoming membership 
     incoming_player_ids = set() 

     try: 
      for member in memberships: 
       incoming_player_ids.add(member['id']) 
     except: 
      raise serializers.ValidationError(
       'id is required field in memberships objects.' 
      ) 

     Membership.objects.filter(
      team_id=instance.id 
     ).delete() 

     # add merchant member mappings 
     Membership.objects.bulk_create(
      [ 
       Membership(
        team_id=instance.id, 
        player_id=player 
       ) 
       for player in incoming_player_ids 
      ] 
     ) 
     return instance 
    else: 
     raise serializers.ValidationError(
       'memberships is not a list of objects' 
      ) 

Ora questo funziona bene per l'aggiornamento dei valori nel database per la tabella di appartenenza. L'unico problema che ho di fronte è che non sono in grado di aggiornare l'istanza prefetched nella memoria, che sulla richiesta PATCH a questa API aggiorna i valori nel database ma la risposta API mostra dati obsoleti.

La successiva richiesta di GET per la stessa risorsa fornisce dati aggiornati. Chiunque abbia lavorato con molti-a-molti in django e abbia scritto metodi di aggiornamento/creazione personalizzati per serializzatori nidificati scrivibili può aiutarmi a capire il modo possibile per risolvere questo problema.

risposta

1

In questo caso sono stato in grado di rintracciare lo issue. Stavo usando .prefetch_related() su queryset che sta causando l'istanza di utilizzare i dati prefetched per molte a molte relazioni. Ho due soluzioni che possono causare altri colpi al database per recuperare i dati aggiornati.

1. Non utilizzare .prefetch_related()

Un modo evidente è di non usare .prefetch_related() Che è non raccomandato in quanto si tradurrà in elevato numero di DB colpisce.

O

2. Recupero esempio modello aggiornato dopo l'aggiornamento molti-a-molti rapporti nel metodo di aggiornamento di serializzatore

instance = self.context['view'].get_queryset().get(
    id=instance.id 
) 

Modificato .update() di TeamSerializer

@transaction.atomic 
def update(self, instance, validated_data): 
    ''' 
    Cutomize the update function for the serializer to update the 
    related_field values. 
    ''' 

    if 'memberships' in validated_data: 
     instance = self._update_membership(instance, validated_data) 

     # remove memberships key from validated_data to use update method of 
     # base serializer class to update model fields 
     validated_data.pop('memberships', None) 

     # fetch updated model instance after updating many-to-many relations 
     instance = self.context['view'].get_queryset().get(
      id=instance.id 
     ) 
    return super(TeamSerializer, self).update(instance, validated_data) 

Se qualcuno ha una migliore approccio mi piacerebbe se possano aggiungere una risposta a questa domanda.

+1

A partire da Django 1.8 c'è una funzione integrata per [aggiornare un oggetto] (https://docs.djangoproject.com/en/1.8/ref/models/instances/#refreshing-objects-from-database). Potresti semplicemente usare 'instance.refresh_from_db()'. – AKS

+1

Ho provato a fare ciò che non funziona con prefetch_related. –

+0

Bene, il mio commento è stato solo per il secondo punto considerando che il primo punto è già in atto. E ancora una cosa: perché stai aggiornando i 'memberships 'da' initial_data' e non da 'validated_data' ?? Non devi usare 'memberships = validated_data.get ('memberships')' nella funzione 'update_membership'? – AKS

2

Penso che sia necessario "ricaricare" il campo giocatori con instance.players.all(), perché il set di query è già stato valutato nel metodo is_valid del serializzatore.

@transaction.atomic 
def update(self, instance, validated_data): 
    ''' 
    Cutomize the update function for the serializer to update the 
    related_field values. 
    ''' 

    if 'memberships' in validated_data: 
     instance = self._update_membership(instance, validated_data) 

     # remove memberships key from validated_data to use update method of 
     # base serializer class to update model fields 
     validated_data.pop('memberships', None) 
     instance.players.all() 

    return super(TeamSerializer, self).update(instance, validated_data) 
+0

instance.players.all() non ha funzionato visto che sto facendo prefetch_related() in queryset. Per evitare l'uso di dati prefetched ho aggiunto un filtro come instance.players.filter (is_active = True) che stampa le iscrizioni aggiornate in console ma l'istanza non mostra ancora dati aggiornati come in precedenza. @ ilse2005 –

+0

Forse questo nuovo metodo aiuta: https://docs.djangoproject.com/en/1.9/ref/models/instances/#refreshing-objects-from-database – ilse2005

+0

Ho provato questo mentre eseguivo il debug del problema ma non l'ho Lavorare per esempio. Ho fatto un lavoro su questo caso. Aggiungerò presto una risposta a questa domanda. –

Problemi correlati