I am using PyCharm 4.5.2, Django 1.8.2.
If I define a class as:
class User(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
slug = models.SlugField(max_length=256, unique=True, default=make_slug)
def make_slug(self):
return self.first_name + self.last_name[0]
The IDE highlights default=make_slug with make_slug being undefined. The interpretter agrees and when the development server tries to refresh it exits with status 1 and the error NameError: name 'make_slug' is not defined.
Because it's just the name of a callable, I can't pass arguments. So if I define the function outside the class (to move into a higher scope and be defined) I can't use the class properties. I have read some suggestions that use lambdas but from the Django documentation that is wrong:
Note that lambdas cannot be used for field options like default
because they cannot be serialized by migrations. See that
documentation for other caveats.
What is the proper way to define a callable for default values in a model.
You shouldn't use this method to set your default value, rather than override the save method of the model and use it there. For example:
class User(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
slug = models.SlugField(max_length=256, unique=True, default=uuid.uuid1)
def make_slug(self):
return self.first_name + self.last_name[0]
def save(self, *args, **kwargs):
self.slug = self.make_slug()
super().save(*args, **kwargs)
You get this error
NameError: name 'make_slug' is not defined.
because you refer to make_slug before you defined it. If you moved the make_slug function above the slug field, then you wouldn't get that error.
However, it isn't possible to pass any arguments to the callable that you use as the default, so that won't work either. You can't get around that restriction by using a model method as you are trying.
If you need access to the model instance to calculate the default, then setting the value in the save() method as ruddra suggests is a good idea. Note that you might want to check whether or not the model has a primary key, so that you only create the slug when you first create the instance.
Related
When I want to create new object from product I got this error:
slugify() got an unexpected keyword argument 'allow_unicode'
This is my models:
class BaseModel(models.Model):
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True,)
slug = models.SlugField(null=True, blank=True, unique=True, allow_unicode=True, max_length=255)
class Meta:
abstract = True
class Product(BaseModel):
author = models.ForeignKey(User)
title = models.CharField()
# overwrite your model save method
def save(self, *args, **kwargs):
title = self.title
# allow_unicode=True for support utf-8 languages
self.slug = slugify(title, allow_unicode=True)
super(Product, self).save(*args, **kwargs)
I also ran the same pattern for other app(blog) ,and there I didn't run into this problem.
What's wrong with this app?
Since the slugify function is working in the other apps, it means that you use a different function that, at least in that file is referenced through the slugify identifier. This can have several reasons:
you imported the wrong slugify function (for example the slugify template filter function [Django-doc];
you did import the correct one, but later in the file you imported another function with the name slugify (perhaps through an alias or through a wildcard import); or
you defined a class or function named slugify in your file (perhaps after importing slugify).
Regardless the reason, it is thus pointing to the "wrong" function, and therefore it can not handle the named argument allow_unicode.
You can resolve that by reorganizing your imports, or giving the function/class name a different name.
Upgrade Django, that argument allow_unicode introduced in the version 1.9, or call the function without that argument.
class ModelA(models.Model):
class Meta:
verbose_name = 'my awesome name'
class ModelB(models.Model):
some_field = models.ForeignKey(ModelA)
Is there a way to make some_field's verbose_name to be my awesome name without being explicit about it? (ie. using the verbose name related class as a default field verbose name, instead of using the attribute name)
I actually think this should be the default Django behaviour.
NOTE: I'm using Django 1.11
Looking into the source code it seems like this is implemented in the set_attributes_from_rel method of the RelatedField class:
if self.verbose_name is None:
self.verbose_name = self.remote_field.model._meta.verbose_name
The verbose_name is also set in the set_attributes_from_name method from the Field class:
if self.verbose_name is None and self.name:
self.verbose_name = self.name.replace('_', ' ')
This method is run first and therefore when the method of the RelatedField class is run self.verbose_name is no longer None. It is unclear to me why the method of the Field class is run first.
Note: I checked the current source code on GitHub which is most likely a newer version than your code as your ForeignKey doesn't have the on_delete parameter which was made required in version 2.0.
I have created a (kind of) singleton to put all the app parameters in my database:
class SingletonModel(models.Model):
def save(self, *args, **kwargs):
self.pk = 1
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
return cls.objects.all().get()
class Meta:
abstract = True
class AppParameters(SingletonModel, models.Model):
DEFAULT_BALANCE_ALERT_THRESHOLD = models.PositiveIntegerField(default=5)
# other parameters...
It worked pretty well, until I tried to use one of these parameters in a default attribute of a model field:
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD,
blank=True,
null=True)
This seemed to work too, but when I use a script to reinitialise local data, the first manage.py migrate produce a DoesNotExist since my Singleton does not exist yet.
It happens because of a file importing Convive model.
How would you solve this?
Is there a way to "delay" the evaluation of the default field?
Thanks.
EDIT
After posting this, I think that if my code processes db queries at import time, something may be wrong with it...
Create a method that returns the default value,
def get_default_balance_alert_threshold():
return AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD
then use that method as your default.
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=get_default_balance_alert_threshold,
blank=True,
null=True,
)
I'm trying to override a save method so that on creation of one model, an instance of the second model is created. However, it looks like the secondary model that I'm trying to create (Restaurant in this example) is being created twice. Why is that?
models.py
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
def save(self, *args, **kwargs):
super(Place, self).save(*args, **kwargs)
if Restaurant.objects.filter(place=self).count() == 0:
restaurant = Restaurant.objects.create(place=self)
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
Your save method does not have proper indentation. I assume this was an error in cut and paste. With in that method.
if Restaurant.objects.filter(place=self).count() == 0:
restaurant = Restaurant.objects.create(restaurant=self)
This is essentially what get_or_create does but does atomically.
This method is atomic assuming correct usage, correct database
configuration, and correct behavior of the underlying database.
However, if uniqueness is not enforced at the database level for the
kwargs used in a get_or_create call (see unique or unique_together),
this method is prone to a race-condition which can result in multiple
rows with the same parameters being inserted simultaneously.
You can do the same in your own code of course with an atomic block but why bother. Just do
Restaurent.objects.get_or_create(place=self)
and isn't that place=self instead of restaurent=self as in your save method?
You can try:
obj.save(commit=False)
#change fields
obj.save()
First you will create save 'instance', do what you have to do, and then call the right save() method.
I want to set a non-persistent property on a model. I have tried the following:
class class User(models.Model):
email = models.EmailField(max_length=254, unique=True, db_index=True)
#property
def client_id(self):
return self.client_id
Then:
user = User.objects.create(email='123', client_id=123)
print(user.client_id)
I get the error: can't set attribute. Why?
You need to define a setter function for your property too, right now, it is read-only (see, e.g., this question).
class class User(models.Model):
email = models.EmailField(max_length=254, unique=True, db_index=True)
#property
def client_id(self):
return self.internal_client_id
#client_id.setter
def client_id(self, value):
self.internal_client_id = value
Note that I renamed self.client_id to self.internal_client_id, because otherwise, you would be calling the getter and setter functions recursively - the name of the internal variable needs to be different from the property's name.
Of course, if you need to use self.client_id (e.g. because of inheritance), you can also rename the property itself.