Django - Substituindo o método Model.create ()?

87

A documentação do Django apenas lista exemplos para sobrescrever save()e delete(). No entanto, gostaria de definir algum processamento extra para meus modelos apenas quando eles forem criados . Para qualquer pessoa familiarizada com Rails, seria o equivalente a criar um :before_createfiltro. Isso é possível?

ground5hark
fonte

Respostas:

160

A substituição __init__()faria com que o código fosse executado sempre que a representação python do objeto fosse instanciada. Não conheço trilhos, mas um :before_createdfiltro me parece um código a ser executado quando o objeto é criado no banco de dados. Se você deseja executar o código quando um novo objeto é criado no banco de dados, você deve sobrescrever save(), verificando se o objeto possui um pkatributo ou não. O código seria mais ou menos assim:

def save(self, *args, **kwargs):
    if not self.pk:
        # This code only happens if the objects is
        # not in the database yet. Otherwise it would
        # have pk
    super(MyModel, self).save(*args, **kwargs)
Zach
fonte
7
Na verdade, encontrei uma solução usando sinais: docs.djangoproject.com/en/dev/topics/signals (o sinal pre_save, especificamente). No entanto, esta parece ser uma solução muito mais pragmática. Muito obrigado.
ground5hark
4
Suponho que você quer dizer substituir o método do gerente create? Essa é uma solução interessante, mas não funcionaria nos casos em que o objeto está sendo criado usando Object(**kwargs).save()ou qualquer outra variação dele.
Zach
3
Não acho que seja um hack. É uma das soluções oficiais.
les
6
Não deveria ser super(MyModel, self).save(*args, **kwargs)?
Mark Chackerian
1
Talvez verificar self.pknão seja a melhor opção para verificar se o objeto está sendo criado recentemente ou apenas atualizando. Às vezes, você fornece o id do objeto no momento da criação (um valor personalizado não gerado pelo banco de dados como KSUID), e isso fará com que esta cláusula nunca seja executada ... Há self._state.addingvalor para ter certeza se está salvando pela primeira vez ou apenas atualizando, que ajuda nesses casos.
Shahinism
22

um exemplo de como criar um sinal post_save (de http://djangosnippets.org/snippets/500/ )

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    """Create a matching profile whenever a user object is created."""
    if created: 
        profile, new = UserProfile.objects.get_or_create(user=instance)

aqui está uma discussão cuidadosa sobre se é melhor usar sinais ou métodos de salvamento personalizados https://web.archive.org/web/20120815022107/http://www.martin-geber.com/thought/2007/10/29/ django-signs-vs-custom-save-method /

Na minha opinião, usar sinais para esta tarefa é mais robusto, fácil de ler, mas mais demorado.

Michael Bylstra
fonte
Esta é a forma preferida em vez de mexer com os internos do objeto, no entanto, se você fizer modificações no modelo em questão, e não apenas criar outro no exemplo acima, não se esqueça de chamarinstance.save() . Portanto, neste caso, também há uma penalidade de desempenho, pois haverá uma consulta INSERT e uma UPDATE no banco de dados.
Mike Shultz
O link para os sinais vs. métodos de salvamento personalizado está quebrado.
Sander Vanden Hautte
21

Isso é antigo, tem uma resposta aceita que funciona (de Zach) e também mais idiomática (de Michael Bylstra), mas como ainda é o primeiro resultado no Google que a maioria das pessoas vê, acho que precisamos de um django moderno com mais práticas recomendadas resposta de estilo aqui :

from django.db.models.signals import post_save

class MyModel(models.Model):
    # ...
    @classmethod
    def post_create(cls, sender, instance, created, *args, **kwargs):
        if not created:
            return
        # ...what needs to happen on create

post_save.connect(MyModel.post_create, sender=MyModel)

O ponto é este:

  1. usar sinais (leia mais aqui na documentação oficial )
  2. use um método para um bom namespacing (se fizer sentido) ... e eu o marquei como em @classmethodvez de @staticmethodporque muito provavelmente você acabará precisando referir-se aos membros da classe estática no código

Ainda mais limpo seria se o Django central tivesse um post_createsinal real . (Imho, se você precisar passar um argumento booleano para alterar o comportamento de um método, isso deve ser 2 métodos.)

NeuronQ
fonte
15

Para responder à pergunta literalmente, o createmétodo em um gerenciador de modelo é uma forma padrão de criar novos objetos no Django. Para substituir, faça algo como

from django.db import models

class MyModelManager(models.Manager):
    def create(self, **obj_data):
        # Do some extra stuff here on the submitted data before saving...
        # For example...
        obj_data['my_field'] = my_computed_value(obj_data['my_other_field'])

        # Now call the super method which does the actual creation
        return super().create(**obj_data) # Python 3 syntax!!

class MyModel(models.model):
    # An example model
    my_field = models.CharField(max_length=250)
    my_other_field = models.CharField(max_length=250)

    objects = MyModelManager()

Neste exemplo, estou substituindo o método do método Manager createpara fazer algum processamento extra antes que a instância seja realmente criada.

NOTA: Codifique como

my_new_instance = MyModel.objects.create(my_field='my_field value')

irá executar este createmétodo modificado , mas o código como

my_new_unsaved_instance = MyModel(my_field='my_field value')

não vou.

Mark Chackerian
fonte
3

A substituição __init__()permitirá que você execute o código quando o modelo for instanciado. Não se esqueça de ligar para os pais __init__().

Ignacio Vazquez-Abrams
fonte
Ah sim, essa foi a resposta. Não sei como eu esqueci isso. Obrigado Ignacio.
ground5hark
1

A resposta preferida está correta, mas o teste para saber se o objeto está sendo criado não funciona se seu modelo deriva de UUIDModel. O campo pk já terá um valor.

Nesse caso, você pode fazer o seguinte:

already_created = MyModel.objects.filter(pk=self.pk).exists()

Julio Carrera
fonte