Serhiy Pustovit
Serhiy Pustovit

Reputation: 191

Django - Create related object on first call

I have some models:

class User(AbstractUser):
    cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True)

class Cart(models.Model):
    products = models.ManyToManyField('product.Product')
    date_updated = models.DateTimeField(auto_now=True, editable=False)

Now I need to create user cart instance at first call.

   if request.user.cart is None:
        request.user.cart = Cart.objects.create()
        request.user.save()

This method is not good for me, because it leads to code duplication (every time I need to import Cart model and check the user's cart is it None or not).

The best way that I can find is AutoOneToOneField in django-annoying, but unfortunately it absolutely broke current field autocomplete in PyCharm.

What is the right way to do that?

P.S. I really don't need to create user cart object at user creation moment. P.P.S. Sorry for my bad English.

UPDATE: Thank you very much! I came to the first code fragment on my own, but it is noob edition script:

class User(AbstractUser):
    _cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')

@property
def cart(self):
    return self._cart # just for test

...end in my view i've got error like "object Cart has no property 'products'". But next code works great

class User(AbstractUser):
    _cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')

@property
def cart(self):
    if not self._cart:
        self._cart = Cart.objects.create()
        self.save(update_fields=('_cart',))
    return self._cart

...except "unresolved attribute reference" warning in view:

if request.user.cart.add(type, pk):
    messages.success(request, 'Added to cart')

but anyway, that warning should be PyCharm bug (do not autocomplete any @property-decorated methods). You are great, thank's a lot!

Upvotes: 0

Views: 1002

Answers (1)

AKX
AKX

Reputation: 169051

There's a couple different ways I can think of off the cuff:

  • A middleware that does the check on every request – clean, but causes extra database hits even if that request doesn't need the cart on the user
  • A separate function get_cart(request) -> Cart that does the check
  • An accessor on your custom User:
class User(AbstractUser):
    _cart = models.OneToOneField('cart.Cart', on_delete=models.SET_NULL, null=True, blank=True, db_column='cart')

    @property
    def cart(self):
        if not self._cart_id: 
             self._cart = ...
             self.save(update_fields=('_cart',))
        return self._cart

Also, you might want to consider

class Cart(models.Model):
    user = models.OneToOneField(User)
    products = models.ManyToManyField('product.Product')
    date_updated = models.DateTimeField(auto_now=True, editable=False)

# and

class User(AbstractUser):

    @cached_property
    def cart(self):
        cart, created = Cart.objects.get_or_create(user=self)
        return cart

instead.

Upvotes: 3

Related Questions