I want to iterate over my model fields within a Django model and check if they are empty string and replace them with a null in the model save() method programmatically. This is because some CharFields need to be unique or have no value.
Example:
class Person(models.Model):
name = models.CharField(blank=True, unique=True, null=True)
nick_name = models.CharField(blank=True, unique=True, null=True)
...
age = models.IntegerField()
def save(self, *args, **kwargs):
for field in self._meta.fields: # get the model fields
if field=='':
field = None
super(Person, self).save(*args, **kwargs)
The above complains about a creation_counter which is unclear why an attempt to compare those values instead of the field value with the empty string is done.
This could be done manually, but I have too many models....
Any suggestions?
edit:
Thanks to everyone who attempted to help me! :D
The solution that seems to work for me is posted by Jazz, but his code isn't showing up in his post. This is my version which is essentially identical, but with an extra check to make sure we are only overriding when necessary:
from django.db.models.Field import CharField as _CharField
class CharField(_CharField):
def get_db_prep_value(self, value, *args, **kwargs):
if self.blank == self.null == self.unique == True and value == '':
value = None
return super(CharField).get_db_prep_value(value, *args, **kwargs)
In your case, I would suggest a custom model field, which subclasses a CharField and ensures that a empty string is converted to None -- overriding get_db_prep_value should do it.
class EmptyStringToNoneField(models.CharField):
def get_prep_value(self, value):
if value == '':
return None
return value
class Person(models.Model):
name = EmptyStringToNoneField(blank=True, unique=True, null=True)
nick_name = EmptyStringToNoneField(blank=True, unique=True, null=True)
https://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Jazz's answer works like a charm for me.
I just want to point out that if you are using South migration tool, the schemamigration command will fail, unless you specify a introspect rule for the new custom field.
For "simple fields", you can simply add the following code to wherever your field is specified.
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^myapp\.stuff\.fields\.SomeNewField"])
Now the South migration should work.
FYI.
Reference: http://south.aeracode.org/wiki/MyFieldsDontWork
Related
Here is the thing.
A branch may contains many products.
A form would post path and branch to product model, in string format.
How could I use like this Product.objects.create(path="path1", branch="branch1") when got the posted data?
or the branch instance must be created in forms.py?
Here is the wrong version: it would raise ValueError: Cannot assign "'branch1'": "Product.branch" must be a "Branch" instance.
class Branch(models.Model):
name = models.CharField(max_length=63, unique=True, blank=True)
class Product(models.Model):
path = models.CharField(max_length=255)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
kwargs['branch'], _ = Branch.objects.get_or_create(name=kwargs['branch'])
super(Product, self).save(*args, **kwargs)
This is not the save issue. The error raises during the assignment of string to branch name. If you want to implement the logic, do it before saving
You can use python property to achieve this, with small modifications as shown below. Don't need to override save method.
models.py
class Branch(models.Model):
name = models.CharField(max_length=63, unique=True)
class Product(models.Model):
path = models.CharField(max_length=255)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
#property
def branch_name(self):
return self.branch.name
#branch_name.setter
def branch_name(self, value):
self.branch, _ = Branch.objects.get_or_create(name=value)
And your create function should be
Product.objects.create(path="path1", branch_name="branch1")
NOTE : It is branch_name and not branch. Also product.branch remains the branch object and poduct.branch_name returns the name of the branch. This will work with updation also. That is product.branch_name to new value updates the branch of the product
When i save the model from admin interface a first time i need to autogenerate the slug. I use slugify and pre_save signal and my slug field have unique=True option. But when i push save button the object slugfield raise validation error (Required field) because field is unique, i think. I thought that pre_save goes before validation. Or i am wrong?
# models.py
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50)
class Meta:
abstract = True
class Post(Slugified):
title = models.Charfield(max_length=50)
#receiver(pre_save, sender=Post)
def save_slug(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
The form automatically generated by the admin sees the slug field as a required field. The pre_save signal receiver works correctly, but the code never tries to save the model as the form doesn't validate.
The solution to this is to all blank values:
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50, blank=True)
This way, the field is not required in the form and the slug gets set before the instance is saved.
Also, please note that your code does not correctly handle duplicate slugs. If two post titles generate identical slugs, an IntegrityError would be raised. This is easier solved in the save method than in the pre_save signal receiver:
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50, blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
while True:
try:
return super(Slugified, self).save(*args, **kwargs)
except IntegrityError:
# generate a new slug
I'm a newbie in Django, so can you help me to understand how the save() method works?
Here's my models:
class Tag(models.Model):
name = models.CharField(verbose_name=u'Tag', max_length=200, unique=True)
class Entry(models.Model):
title = models.CharField(verbose_name=u'Entry title', max_length=200)
# some more fields here
tags_string = models.CharField(verbose_name=u'Tags', max_length=200, blank=True)
tags = models.ManyToManyField(Tag, blank=True)
There is tags_string where user enters tags separated by comma. It's just a string.
Then I'm trying to add tags to ManyToManyField by clicking "Save" in Django admin:
def save(self):
super(Entry, self).save()
if self.tags_string:
for tag in tags_string.split(","):
t = Tag.objects.create(name=tag)
self.tags.add(t)
but it doesn't work. entry.tags.add(t) works perfectly through the Django shell - it adds the values to the database. I think that something is wrong in my save() method.
Could you suggest me how to fix it, please?
try this
def save(self):
super(Entry, self).save()
if self.tags_string:
for tag in tags_string.split(","):
self.tags.create(name=tag)
Check the M2M tags format and print those?
def save(self):
super(Entry, self).save()
if self.tags_string:
print self.tags,type(self.tags)
for tag in tags_string.split(","):
.......
First, save has additional parameters that you need to account for. Second, you should be using get_or_create instead of create for the tags:
def save(self, *args, **kwargs):
super(Entry, self).save(*args, **kwargs)
if self.tags_string:
for tag in tags_string.split(","):
t, created = Tag.objects.get_or_create(name=tag)
self.tags.add(t)
Those may not fix the current issue, but it would have got you eventually.
You should also probably be doing some sort of normalization on the tags as well, using str.lower() or title() from django.template.defaultfilters. Otherwise, you'll end up with "Tag", "tag", "TAG" and "tAg".
I generate field automaticly, so I want to hide it from user. I've tried editable = False and hide it from exclude = ('field',). All this things hide this field from me, but made it empty so I've got error: null value in column "date" violates not-null constraint.
models.py:
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(editable=False)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)`
admin.py:
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['date'] = datetime.now()
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
list_display = ('title','user',)
list_filter = ('date',)
Based on your model setup, I think the easiest thing to do would change your date field to:
date = models.DateTimeField(auto_now=True)
that should accomplish what you're after and you don't even need to exclude it from the admin, it's excluded by default. If you have auto_now=True it will act as a 'last update time'. If you have auto_now_add=True it will act as a creation time stamp.
There are several other ways you could accomplish your goal if your use case is more complex than a simple auto date field.
Override the model's save method to put the value in.
class Message(models.Model):
title=models.CharField(max_length=100)
date = models.DateTimeField(editable=False)
def save(*args, **kwargs):
self.date = datetime.datetime.now()
super(Message, self).save(*args, **kwargs)
What you are trying to do with the Model Admin isn't quite working because by default django only transfers the form fields back to a model instance if the fields are included. I think this might be so the model form doesn't try to assign arbitrary attributes to the model. The correct way to accomplish this would be to set the value on the instance in your form's save method.
class MessageAdminForm(forms.ModelForm):
def save(*args, **kwargs):
self.instance.date = datetime.now()
return super(MessageAdminForm, self).save(*args, **kwargs)
2 questions:
How can I stop duplicates from being created when parent=None and name is the same?
Can i call a model method from within the form?
Please see full details below:
models.py
class MyTest(models.Model):
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=255, blank=True, unique=True)
owner = models.ForeignKey(User, null=True)
class Meta:
unique_together = ("parent", "name")
def save(self, *args, **kwargs):
self.slug = self.make_slug()
super(MyTest, self).save(*args, **kwargs)
def make_slug(self):
# some stuff here
return generated_slug
note: slug = unique as well!
forms.py
class MyTestForm(forms.ModelForm):
class Meta:
model = MyTest
exclude = ('slug',)
def clean_name(self):
name = self.cleaned_data.get("name")
parent = self.cleaned_data.get("parent")
if parent is None:
# this doesn't work when MODIFYING existing elements!
if len(MyTest.objects.filter(name = name, parent = None)) > 0:
raise forms.ValidationError("name not unique")
return name
Details
The unique_together contraint works perfectly w/ the form when parent != None. However when parent == None (null) it allows duplicates to be created.
In order to try and avoid this, i tried using the form and defined clean_name to attempt to check for duplicates. This works when creating new objects, but doesn't work when modifying existing objects.
Someone had mentioned i should use commit=False on the ModelForm's .save, but I couldn't figure out how to do/implement this. I also thought about using the ModelForm's has_changed to detect changes to a model and allow them, but has_changed returns true on newly created objects with the form as well. help!
Also, (somewhat a completely different question) can I access the make_slug() model method from the Form? I believe that currently my exclude = ('slug',) line is also ignoring the 'unique' constraint on the slug field, and in the models save field, I'm generating the slug instead. I was wondering if i could do this in the forms.py instead?
You could have a different form whether you are creating or updating.
Use the instance kwarg when instantiating the form.
if slug:
instance = MyTest.object.get( slug=slug )
form = MyUpdateTestForm( instance=instance )
else:
form = MyTestForm()
For the second part, I think that's where you could bring in commit=False, something like:
if form.is_valid():
inst = form.save( commit=False )
inst.slug = inst.make_slug()
inst.save()
I don't know for sure this will fix your problem, but I suggest testing your code on the latest Django trunk code. Get it with:
svn co http://code.djangoproject.com/svn/django/trunk/
There have been several fixes to unique_together since the release of 1.02, for example see ticket 9493.
Unique together should be a tuple of tuples
unique_together = (("parent", "name"),)