quite a generic question here but I was curious as to when and in what cases would you use a save method in a ModelForm over the Model class itself and vice versa?
model:
class Model(models.Model):
...
def save(self, *args, **kwargs):
super (Model, self).save(*args, **kwargs)
modelform:
class ModelForm(forms.ModelForm):
...
def save(self, commit=True):
model = super(ModelForm, self).save(commit=True)
if commit:
model.save()
return model
Thanks!
Both are overwriting the default method. Only difference is that Model.save is saving object attributes but ModelForm.save is saving form data. ModelForm is not just a simple Form. It linked to model instance. So it automatically access the fields of corresponding models.
The difference depends on your need/usage. If you know objects will be always be created using ModelForm then you put your logic in ModelForm.save to do stuff which is required before or after saving the data. If you know the objects could be created directly as well which obviously does not go through ModelForm you then write your logic in Model.save to ensure integrirty/consistentency of data. But this does not mean that overriding save both on ModelForm and Model itself is bad, again there will be scenarios where you need both.
Related
I was thinking of creating an instance of a foreignkey field and referring it every time an instance of a model is created, but I didn't find any solution for this. Usually we need to create a model of foreignkey type and then refer to it from the model, but I want it to automatically create one foreignkey instance from the beginning of and always refer to it.
To provide an example let's say we've 2 model fields called User and WeeklySchedule. Everytime an instance of a User is created,another corresponding instance of a WeeklySchedule model is also created that will be referred to by the instance of the User.
We can do this inside save() method of the User model.
def save(self, *args, **kwargs):
schedule = create_and_or_get_new_weekly_schedule()
""" where create_and_or_get_new_weekly_schedule either creates a new
instance or gets that of the foreignkey model
"""
self.availability_schedule_tutor = schedule
super().save(*args, **kwargs)
We can also set the on_delete option of the foreignkey field to models.PROTECT or models.RESTRICT to make sure it never loses reference. Also make sure to set null=True or else an instance of a user can never be created.
Something like the following:
weekly_schedule = WeeklySchedule()
# Set your weekly_schedule fields here
weekly_schedule.save()
user = User()
# Set your user fields here
user.weekly_schedule = weekly_schedule
user.save()
let's say I have this model:
class MyModel(models.Model):
char_field = models.CharField(max_length=64)
json_field = LimitedJSONField(default={})
where LimitedJSONField is a custom field for storing JSONStrings on DB.
I would like to do pre-save check on json_field (e.g. truncate its length if exceeding). I have read about overriding save method for MyModel, I also know I can implement a pre-save signal but I would like to handle it on field-level. Because let's say I use LimitedJSONField on 500 models. Do I have to override save method for each of those 500 models? I implemented a validate method on LimitedJSONField but it does not get triggered on save (it's triggered only on form validation, i.e. full_clean routine).
How can I implement a validator for LimitedJSONField, so that whatever Model uses it, this field gets validated with regards to one single business logic written inside LimitedJSONField?
Put simply, I would like to implement the logic within field class and I would like to have no logic written in Model class, so that the solution is scalable for new Model classes to use this field without needing to implement boilerplace logic code.
Thanks a lot for your time!
Could you make a parent class with a single save method and use it as a mixin that is inherited by all of your other models?
Something like:
class SpecialJsonModel(models.model):
json_field = LimitedJSONField(default={})
def save(self, *args, **kwargs):
// Specific save logic goes here
class OtherModelA(SpecialJsonModel)
char_field = models.CharField(max_length=64)
class OtherModelB(SpecialJsonModel)
char_field = models.CharField(max_length=64)
Then you would only have to write one overridden save method.
I have this model
class Env(models.Model):
functional_count = models.PositiveIntegerField()
current_count = models.PositiveIntegerField()
Now i want functional_count to always be less than current_count.
So during create,
def form_valid(self, form):
form.instance.current_count = 0
This is because i want current_count during initialization.Then my python code never allows current_count to go above functional_count.
The problem comes in Update.
class EnvUpdate(UpdateView):
model = Capacity.models.Envapps
fields = ['functional_count']
template_name_suffix = '_update_form'
So do i include a validator? If yes, where and how?
Or other option is to verify in get_success_url().
Any other solution?
Assuming your updates come through a form (as suggested by the use of form_valid(), perhaps you can use the form clean() method, as described in the documentation. This allows you to perform checks for fields that depend on each other. The documentation also has an example which should get you further.
Update
From your comments, I understand that you tried to use clean() inside EnvUpdate, which inherits from the class-based UpdateView view. All the mixins provided through UpdateView do apparently not provide a clean() method, so you can't override it.
I am actually referring to the clean() in a form class (as follows from the link). So, it looks like you'd need to create your own ModelForm class, something like:
class EnvappsForm(forms.ModelForm):
class Meta:
model = Capacity.models.Envapps
fields = ['functional_count']
def clean(self):
cleaned_data = super(ContactForm, self).clean()
if cleaned_data['functional_count'] >= form.instance.current_count:
raise ValidationError('too large')
return cleaned_data
and then in your view:
class EnvUpdate(UpdateView):
model = Capacity.models.Envapps
template_name_suffix = '_update_form'
form_class = EnvappsForm
Note: this is completely untested! I don't know if the comparison in the clean() works (i.e., if form.instance.current_count can be found), and wheter EnvUpdate will override the form_class (it shouldn't, but I've never tried). It just might be even possible that you can remove the meta subclass, and provide the model and fields from EnvUpdate, as you do yourself above. That's just something you can easily try out.
If functional_count should always be less than current_count you should check it in the clean() method on the model and not some random ModelForm. The model clean() will be called during normal ModelForm validation.
I have the following models:
class Recipe(models.Model):
fields...
class Ingredient(models.Model):
fields...
class UsesIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
amount = models.FloatField()
group = models.CharField()
I have a view which lets the user add any number of 'UsesIngredient' models for a certain recipe through a dynamic formset. The group attribute is automatically filled in an hidden from the user.
The problem is that when the users adds a new form in the formset, but doesn't fill in any of the fields, I don't want that form saved. However, django still tries to save the form because the 'group' attribute has 'changed' (because it has been automatically filled in when the extra form was created).
Is there any way to get around this?
Thanks!
Well, I still didn't feel completely comfortable with Tim Edgar's solution, so I kept looking. I guess I found what I was looking for.
The 'Form' class, has two undocumented methods that are of use in this case: 'has_changed()' and '_get_changed_data'.
During ModelFormSet validation, every form checks 'has_changed()'. If the form did not changed, validation is skipped and a correct form is assumed.
Likewise, during ModelFormSet saving, the save_new_objects checks every form to see if it has changed. If it didn't change, the form isn't saved.
So my solution was to override the has_changed() method to return False if only the 'group' attribute has changed, and all other fields are empty. This is my implementation:
class UsesIngredientForm(forms.ModelForm):
class Meta:
model = UsesIngredient
def has_changed(self, *args, **kwargs):
self._get_changed_data(*args, **kwargs)
# If group is in changed_data, but no other fields are filled in, remove group so
# the form will not be validated or saved
if 'group' in self._changed_data and len(self._changed_data) == 1:
contains_data = False
for name in ['ingredient', 'amount', 'unit']:
field = self.fields[name]
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if data_value:
contains_data = True
break
if not contains_data:
self._changed_data.remove('group')
return bool(self._changed_data)
Hope this helps anybody in the future!
EDIT:
I edited this answer to reflect Tim Edgars comment.
I realize that this implementation still uses 'private' methods, but I haven't found a cleaner implementation using just the publicly documented methods. But then maybe that is just my own incompetence :).
You could try making all your fields to require a value by setting blank=False. See more here. It should require validation that the values that you care about are not left blank.
If that doesn't work, you can try creating your own custom save method that does the validation that you care about.
def save(self, *args, **kwargs):
# Do your checks on the properties such as self.group, self.amount, etc
# If it is fine then call
super(UsesIngredient, self).save(*args, **kwargs)
I want to alter properties of a model field inherited from a base class. The way I try this below does not seem to have any effect. Any ideas?
def __init__(self, *args, **kwargs):
super(SomeModel, self).__init__(*args, **kwargs)
f = self._meta.get_field('some_field')
f.blank = True
f.help_text = 'This is optional'
So.. You need to change blank and help_text attributes.. And I assume that you want this feature just so the help_text is displayed in forms, and form does not raise "this field is required"
So do this in forms:
class MyForm(ModelForm):
class Meta:
model = YourModel
some_field = forms.CharField(required=False, help_text="Whatever you want")
OK, that's simply not possible, here is why:
http://docs.djangoproject.com/en/1.1/topics/db/models/#field-name-hiding-is-not-permitted
EDIT:
And by the way: don't try to change class properties inside a constructor, it's not a wise thing to do. Basically what you are trying to do, is to change the table, when you are creating a row. You wouldn't do that, if you were just using SQL, would you :)? Completely different thing is changing forms that way - I often dynamically change instance a form, but then I still change only this one instance, not the whole template (a class) of form to be used (for example to dynamically add a field, that is required in this instance of a form).