2016-12-16 2 views
1

Je le code suivant dans accounts/signals/__init__.py:Django: ne peut pas obtenir une instance modèle de ForwardManyToOneDescriptor (ForeignKey)

from django.db.models.signals import post_save 
from django.dispatch import receiver 
from orders.models import Order 
from accounts.models import Balance 

@receiver(post_save, sender=Order) 
def update_referral_balance(sender, **kwargs): 
    if len(sender.user.referrals_set.all()): 
     # TODO: Add referralTransaction 
     new_referral_revenue = sender.user.referrals_set.get().revenue 
     revenue_from_trade = \ 
      new_referral_revenue - sender.old_referral_revenue 

     balance, created = \ 
      Balance.objects.get(user=sender.user, currency=sender.currency) 
     balance.balance += revenue_from_trade 
     balance.save() 

Maintenant, lorsque les tests en cours d'exécution, je reçois les éléments suivants

error:====================================================================== 
    ERROR: test_orders_with_approved_payments (payments.tests.test_views.PaymentReleaseTestCase) 
    ---------------------------------------------------------------------- 
    Traceback (most recent call last): 
     File "/pipeline/source/payments/tests/test_views.py", line 75, in setUp 
     self.order.save() 
     File "/pipeline/source/orders/models.py", line 63, in save 
     super(Order, self).save(*args, **kwargs) 
     File "/usr/local/lib/python3.5/site-packages/safedelete/models.py", line 64, in save 
     super(Model, self).save(**kwargs) 
     File "/usr/local/lib/python3.5/site-packages/django/db/models/base.py", line 708, in save 
     force_update=force_update, update_fields=update_fields) 
     File "/usr/local/lib/python3.5/site-packages/django/db/models/base.py", line 745, in save_base 
     update_fields=update_fields, raw=raw, using=using) 
     File "/usr/local/lib/python3.5/site-packages/django/dispatch/dispatcher.py", line 192, in send 
     response = receiver(signal=self, sender=sender, **named) 
     File "/pipeline/source/accounts/signals/__init__.py", line 9, in update_referral_balance 
     if len(sender.user.referral_set.all()): 
    AttributeError: 'ForwardManyToOneDescriptor' object has no attribute 'referral_set' 

Et en effet, lors de l'exécution dans le débogueur, je vois que l'attribut sender.user est quelque chose d'exemple ForwardManyToOneDescriptor:

ipdb> pprint(sender.__dict__['user'].__dict__) 
{'cache_name': '_user_cache', 
'field': <django.db.models.fields.related.ForeignKey: user>} 

Qu'est-ce que je fais mal?

EDIT: Mon Order Modèle:

classe Order (TimeStampedModel, SoftDeletableModel, UniqueFieldMixin): USD = "USD" RUB = "RUB" EUR = "EUR"

BUY = 1 
SELL = 0 
TYPES = (
    (SELL, 'SELL'), 
    (BUY, 'BUY'), 
) 

# Todo: inherit from BTC base?, move lengths to settings? 
order_type = models.IntegerField(choices=TYPES, default=BUY) 
amount_cash = models.DecimalField(max_digits=12, decimal_places=2) 
amount_btc = models.DecimalField(max_digits=18, decimal_places=8) 
currency = models.ForeignKey(Currency) 
payment_window = models.IntegerField(default=settings.PAYMENT_WINDOW) 
user = models.ForeignKey(User, related_name='orders') 
is_paid = models.BooleanField(default=False) 
is_released = models.BooleanField(default=False) 
is_completed = models.BooleanField(default=False) 
is_failed = models.BooleanField(default=False) 
unique_reference = models.CharField(
    max_length=settings.UNIQUE_REFERENCE_LENGTH, unique=True) 
admin_comment = models.CharField(max_length=200) 
payment_preference = models.ForeignKey('payments.PaymentPreference', 
             default=None, 
             null=True) 

class Meta: 
    ordering = ['-created_on'] 

def save(self, *args, **kwargs): 
    self.unique_reference = \ 
     self.gen_unique_value(
      lambda x: get_random_string(x), 
      lambda x: Order.objects.filter(unique_reference=x).count(), 
      settings.UNIQUE_REFERENCE_LENGTH 
     ) 
    self.convert_coin_to_cash() 

    if 'is_completed' in kwargs and\ 
      kwargs['is_completed'] and\ 
      not self.is_completed: 
     self.old_referral_revenue = \ 
      self.user.referral_set.get().revenue 

    super(Order, self).save(*args, **kwargs) 

def convert_coin_to_cash(self): 
    self.amount_btc = Decimal(self.amount_btc) 
    queryset = Price.objects.filter().order_by('-id')[:2] 
    price_sell = [price for price in queryset if price.type == Price.SELL] 
    price_buy = [price for price in queryset if price.type == Price.BUY] 

    # Below calculation affect real money the client pays 
    assert all([len(price_sell), 
       price_sell[0].price_usd, 
       price_buy[0].price_rub, 
       price_buy[0].price_eur]) 

    assert all([len(price_buy), 
       price_buy[0].price_usd, 
       price_buy[0].price_rub, 
       price_buy[0].price_eur]) 

    # TODO: Make this logic more generic, 
    # TODO: migrate to using currency through payment_preference 

    # SELL 
    self.amount_cash = Decimal(self.amount_btc) 

    if self.order_type == Order.SELL and self.currency.code == Order.USD: 
     self.amount_cash *= price_buy[0].price_usd 

    elif self.order_type == Order.SELL and self.currency.code == Order.RUB: 
     self.amount_cash *= price_buy[0].price_rub 

    elif self.order_type == Order.SELL and self.currency.code == Order.EUR: 
     self.amount_cash *= price_buy[0].price_eur 

    # BUY 
    if self.order_type == Order.BUY and self.currency.code == Order.USD: 
     self.amount_cash *= price_sell[0].price_usd 

    elif self.order_type == Order.BUY and self.currency.code == Order.RUB: 
     self.amount_cash *= price_sell[0].price_rub 

    elif self.order_type == Order.BUY and self.currency.code == Order.EUR: 
     self.amount_cash *= price_sell[0].price_eur 

    self.amount_cash = money_format(self.amount_cash) 

@property 
def is_buy(self): 
    return self.order_type 

@property 
def payment_deadline(self): 
    """returns datetime of payment_deadline (creation + payment_window)""" 
    # TODO: Use this for pay until message on 'order success' screen 
    return self.created_on + timedelta(minutes=self.payment_window) 

@property 
def expired(self): 
    """Is expired if payment_deadline is exceeded and it's not paid yet""" 
    # TODO: validate this business rule 
    # TODO: Refactor, it is unreasonable to have different standards of 
    # time in the DB 
    return (timezone.now() > self.payment_deadline) and\ 
      (not self.is_paid) and not self.is_released 

@property 
def payment_status_frozen(self): 
    """return a boolean indicating if order can be updated 
    Order is frozen if it is expired or has been paid 
    """ 
    # TODO: validate this business rule 
    return self.expired or \ 
     (self.is_paid and 
     self.payment_set.last() and 
     self.payment_set.last(). 
     payment_preference. 
     payment_method.is_internal) 

@property 
def withdrawal_address_frozen(self): 
    """return bool whether the withdraw address can 
     be changed""" 
    return self.is_released 

@property 
def has_withdraw_address(self): 
    """return a boolean indicating if order has a withdraw adrress defined 
    """ 
    # TODO: Validate this business rule 
    return len(self.address_set.all()) > 0 

@property 
def withdraw_address(self): 
    addr = None 

    if self.has_withdraw_address: 
     addr = self.transaction_set.first().address_to.address 

    return addr 

def __str__(self): 
    return "{} {} {} BTC {} {}".format(self.user.username or 
             self.user.profile.phone, 
             self.order_type, 
             self.amount_btc, 
             self.amount_cash, 
             self.currency) 
+0

Vous devez montrer votre modèle de commande. –

+0

@DanielRoseman Fait! Merci pour votre commentaire! –

Répondre

3

Le sender argument est le modèle classe le signal s'est connecté à. Comme vous pouvez le voir à partir du signals docs, dans post_save l'instance est passée dans un argument distinct sans surprise appelé instance.

Vous devez écrire votre gestionnaire comme ceci:

@receiver(post_save, sender=Order) 
def update_referral_balance(sender, instance, **kwargs): 
    if len(instance.user.referrals_set.all()): 

etc, en changeant sender-instance tout au long.

+0

Stupide moi ...... –

+0

Bien la documentation est naturellement énormément longue ... J'attendais sur ce projet docs StackOverflow ... mais il n'est jamais venu –