Populating default value from another table - django

At a bit of a loss here. Basically I have a Product page in the admin section and at the bottom of that I have an TabularInline that let's me choose sizes for the product and set a price. This works but I'm trying to get it so that it pulls the default price from the Sizes table to be the initial value.
I tried the following:
class Size(models.Model):
name = models.CharField(max_length=50)
default_price = models.DecimalField(max_digits=9, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'sizes'
ordering = ['-created_at']
verbose_name_plural = 'Sizes'
def __unicode__(self):
return self.name
def default_price(self):
return self.default_price
And then
class ProductOption(models.Model):
product = models.ForeignKey(Product)
size = models.ForeignKey(Size)
price = models.DecimalField(max_digits=9, decimal_places=2, default=Size.default_price)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
It validates, but when I load the admin page I get the following:
unbound method get_default_price() must be called with Size instance as first argument (got nothing instead)
Not sure where else to go from here. I've been looking over the Django docs forever trying to find something else to try. Any ideas?

So, you can override the save method, and if there is no self.id, you can populate some other fields first, that's what sticks out at me as the best solution.
just as an example:
def save(*args, **kwargs):
if not self.id:
self.size = SomeOtherModel.objects.get(pk=size)
super(Size, self).save(*args, **kwargs)

You have several issues here:
Size.default_price is a callable object, so python is trying to call it when setting the default value of your ProductOption. What you might be looking for is:
class Size(models.Model):
# Other stuff here
#property
def default_price(self):
return self.default_price
However, this then causes another, deeper error. If you had the following:
size = Size()
size.default_price
You would wind up with a StackOverflow, due to infinite recursion. This property calls itself. A solution is to have the property name different to the attribute, or in this simplest case, it already acts a bit like a property. You can just remove the whole default_price definition.
However, you are calling Size.default_price, which is not bound to anything if you call it on Size, a class, not on an instance of that class.
What you want to actually do is kind of what Issac Kelly suggests, but more so.
class ProductOption(models.Model):
# column definitions
def __init__(self, *args, **kwargs):
super(ProductOption, self).__init__(*args, **kwargs)
if self.size and not self.price:
self.price = self.size.default_price
This means that when you instantiate a ProductOption, it will automatically set the price based on the size, if there isn't already a price.
You might also want to do this check at save time, as otherwise:
po = ProductOption()
po.size = Size.objects.get(pk=1)
po.save()
# po.price is not set now!
po = ProductOption.objects.get(pk=po.pk)
# po.price is set now!
So, you could have the following:
class ProductOption(models.Model):
# columns
def __init__(self, *args, **kwargs):
super(ProductOption, self).__init__(*args, **kwargs)
self.set_default_price()
def save(self, *args, **kwargs):
self.set_default_price()
super(ProductOption, self).save(*args, **kwargs)
def set_default_price(self):
if self.size and not self.price:
self.price = self.size.default_price
Note the difference in order of the super() call: when initialising the object, we want to call the parent's __init__ first, but when saving, we want to (possibly) set the default value first, then call the parent class's save method.

Related

In Django models, can I fetch the value of a field, process it and store it in another field?

I want to reference the value of a field in another field.
I've tried default=self.key_name * 2, but that won't work.
class Pizza_size(models.Model):
length = models.DecimalField(decimal_places=2, default=0)
price = models.DecimalField(decimal_places=2, default=self.length*2)
When I create an object with the Pizza_size class, if the value of length is 5, I want the value of price to be 10 by just running the command Pizza_size(length=5).
According to the docs, "A classic use-case for overriding the built-in methods is if you want something to happen whenever you save an object."
Given what it seems you are trying to accomplish, the correct way to handle this would be to override the save method for the model.
Here's an example:
class Pizza_size(models.Model):
length = models.DecimalField(decimal_places=2, default=0)
price = models.DecimalField(decimal_places=2, default=0)
def save(self, *args, **kwargs):
self.price = self.length*2
super(MyModel, self).save(*args, **kwargs)
Another option would be to use the pre_save signal, but that is not as simple or standard as the above.

Django model audit mixin

Hello I wanted to know how to create a few fields and convert them into a mixin.
Let's say I have the following.
class Supplier(models.Model):
name = models.CharField(max_length=128)
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
I want to create a mixin to avoid writing every time the last 4 fields into different models.
Here is the mixin I would create:
class AuditMixin(models.Model):
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
class Supplier(AuditMixin):
name = models.Charfield(max_length=128)
How can I make sure that the related_name is relevant to the class the mixin is included into? Also in the save function, How can I make sure the class the mixin is included into is returned (as per the last line)?
Thank you!
Firstly, in any super call, you must always use the current class. So it will always be super(AuditMixin, self)... and your question does not apply.
Django itself takes care of substituting the current class name in related_name if you use the %(class)s syntax, which you have, so again there is nothing else for you to do. See the model inheritance docs.

Django Unique Slug by id

class Product(models.Model):
title = models.CharField(max_length=75)
class Deal(models.Model):
product = models.ForeignKey(Product)
slug = models.SlugField(max_length=255, unique=True)
Having a similar basic setup as above, I want to generate unique slugs for each Deal instance using product title of it's deal and id of the deal itself. IE: "apple-iphone-4s-161" where 161 is the id of the deal and the text before is the title of the product.
For this, how can I overwrite the save() method of the Deal model to apply it?
Of course you can simply overwrite save() method on model (or make receiver for post_save signal).
It will be something like:
from django.template.defaultfilters import slugify
class Deal(models.Model):
product = models.ForeignKey(Product)
slug = models.SlugField(max_length=255, unique=True)
def save(self, *args, **kwargs):
super(Deal, self).save(*args, **kwargs)
if not self.slug:
self.slug = slugify(self.product.title) + "-" + str(self.id)
self.save()
But what is ugly in this solution is that it will hit database twice (it is saved two times). It is because when creating new Deal object it will not have id until you save it for the first time (and you cannot do much about it).
i've bumped at this problem and tested the jasisz solution, and got the max recursion depth exceeded error, so i've fiddle it little and this is how looks for me:
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Node, self).save(*args, **kwargs)
You could edit this to suit your needs, it tests if this records exists, if not then it creates the slug field otherwise is update and no need for modifieng the slug field.
hope it helps.
You should not have your id in that slug field at all. The two main reasons for this are:
It creates the problem you are having
It's redundant data in the database – the id is already stored
But there is a simple solution to your problem: The only reason why the slug is stored in the database is so you can find a row by slug. But you don't need that – you have the ID. So you should do this:
class DealManager(models.Manager):
def get_by_slug(slug):
return self.get(id=slug.rsplit('-', 1)[1])
class Deal(models.Model):
product = models.ForeignKey(Product)
objects = DealManager()
#property
def slug(self):
return slugify(f'{self.name}-f{self.id}')
In your view or wherever you need to retrieve an item given a slug, you can just do Deal.objects.get_by_slug(slug).
I know this may not exactly work for your situation, but I remember bumping into a similar case. I think I had a created_at field in my model that has auto_now=True. Or something simillar
My slug would look like like this
self.slug = '%s-%s' %(
slugify(self.title),
self.created_at
)
or you can have
self.slug = '%s-%s' %(
slugify(self.title),
datetime.datetime.now()
)
just make sure that the slug max_length is long enough to include the full created_at time, as well as the title, so that you don't end up with non-unique or over max length exceptions.

Create objects from one model based on new object in another model (Django)

Fairly new to Django (1.2), but just testing stuff out.
My headache is figuring out a way to create a series of SKU objects when a Product object is created.
class Product(models.Model):
name = models.CharField(max_length=255, unique=True)
style = models.CharField(max_length=50)
colors = models.ManyToManyField(Color)
sizes = models.ManyToManyField(Size)
class SKU(models.Model):
id = models.CharField(max_length=15, unique=True, primary_key=True)
style = models.ForeignKey(Product)
color = models.ForeignKey(Color)
size = models.ForeignKey(Size)
quantity = models.IntegerField(default=0)
I initially tried to handle this in Product.save, but alas, no dice. I then tried this signals setup:
# models.py
def create_skus(sender, instance, *args, **kwargs):
if 'created' in kwargs:
if kwargs['created']:
for x in instance.colors.select_related():
for y in instance.sizes.select_related():
sku = instance.style
sku += instance.x.shortcode
sku += instance.y.name
s = SKU(id=sku, style=instance, color=x, size=y)
s.save()
... # after the Product class
post_save.connect(create_skus, sender=Product)
Some examples I've seen call for the sender class save method to have something like this:
def save(self):
post_save.send(create_skus, self)
...and that's where I'm at. This setup doesn't throw any errors, but it also doesn't create said SKUs.
If I'm way off on any of this signals-wise, any pointers would be appreciated. Or if there's another technique to accomplish a similar feat, that would be wonderful as well.
Cheers!
def create_skus(sender, instance, *args, **kwargs):
if kwargs['created']:
for color in instance.colors.select_related():
for size in instance.sizes.select_related():
sku = u'%s%s%s' % (instance.style, color.shortcode, size.name)
s = SKU(id=sku, style=instance, color=color, size=size)
s.save()
post_save.connect(create_skus, sender=Product)
This should work? If not, try installing the Debug Toolbar and checking that select_related() is returning the results you are expecting.

Adding custom field and updating model problem in django

I need to add a colorpicker to my django model and wrote a custom widget. However when I add this colordfield to my model, django gives this error:
column mediaplanner_ievent.color does not exist
LINE 1: ...nt"."bits", "mediaplanner_ievent"."capture_link", "mediaplan...
My model is :
from mediaplanner.custom_widgets import ColorPickerWidget
class ColorField(models.CharField):
def __init__(self,*args, **kwargs):
kwargs['max_length'] = 10
super(ColorField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
kwargs['widget'] = ColorPickerWidget
return super(ColorField, self).formfield(**kwargs)
class iEvent(models.Model):
name = models.CharField(verbose_name= u"Uygulama Adı", max_length=100, unique=True)
bits = models.CommaSeparatedIntegerField(verbose_name= u"Bitler",max_length=100)
capture_link = models.URLField(verbose_name= u"Capture URL", null=True, blank=True)
color = ColorField(blank=true)
class Meta:
verbose_name = u"red button"
verbose_name_plural = u"red buttonlar"
def __unicode__(self):
return smart_str( "%s"% self.name )
The strange thing is, when I looked my database, there exist colorfield. I don't want to delete the db and load it again. But ofcourse if it's the only solution, then no choice ..
So someone can help me how to solve it?
The field in your database is named colorfield bu the field in your model is named color. You have to change one or the other to make it work again.