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).
Related
I want to set a queryset on a ManyToManyField to get all objects that have a ForeignKey equal to ForeignKey set in another formfield.
class ProductAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields['variants'].queryset = Variant.objects.filter(variantCategory__productCategory__name_single='test')
This works, It selects all "Variant"'s where value of "name_single" of productCategory in its variantCategory is equal to 'test'. I want to replace 'test' to be equal to "name_equal" of productCategory set in this very form.
Here is some code to ilustrate how the objects relate to eachother.
class Variant(models.Model):
variantCategory = models.ForeignKey('VariantCategory')
class VariantCategory(models.Model):
productCategory = models.ForeignKey('ProductCategory')
class ProductCategory(models.Model):
name_single = models.CharField()
https://imgur.com/a/OLah8at
This shows the field that should define the productCategory to equal to.
I realize this is kind of hard to explain, if any of you need extra code/info let me know!
Also, I just need the query to get all objects where productCategory equals to another productCategory. The "name_equal" part doesn't really matter, could be the id of the object to!
I think you should use instance from "form".
Try something like this.
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['variants'].queryset = Variant.objects.select_related('variantCategory__productCategory').filter(variantCategory__productCategory_id=self.instance.productCategory_id)
This solution won't work if you add new Product, because you won't have instance (instance will be only on memory, not database) so you couldn't match foreign_key. As u can see i added select_related. Bellow some good practice
remember to use select_related:
Variant.objects.select_related('variantCategory__productCategory').filter(variantCategory__productCategory_id=instance.productCategory_id)
use model_id instaed of model.id
Variant.objects.select_related('variantCategory__productCategory').filter(variantCategory__productCategory_id=instance.productCategory_id)
If you just need the query to get all objects where productCategory equals to another productCategory try this:
myProductCategory = ProductCategory.objects.last()
Product.objects.filter(productCategory__id=myProductCategory.id)
Let me know if it works :)
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 a Django model where a lot of fields are choices. So I had to write a lot of "is_something" properties of the class to check whether the instance value is equal to some choice value. Something along the lines of:
class MyModel(models.Model):
some_choicefield = models.IntegerField(choices=SOME_CHOICES)
#property
def is_some_value(self):
return self.some_choicefield == SOME_CHOICES.SOME_CHOICE_VALUE
# a lot of these...
In order to automate this and spare me a lot of redundant code, I thought about patching the instance at creation, with a function that adds a bunch of methods that do the checks.
The code became as follows (I'm assuming there's a "normalize" function that makes the label of the choice a usable function name):
def dynamic_add_checks(instance, field):
if hasattr(field, 'choices'):
choices = getattr(field, 'choices')
for (value,label) in choices:
def fun(instance):
return getattr(instance, field.name) == value
normalized_func_name = "is_%s_%s" % (field.name, normalize(label))
setattr(instance, normalized_func_name, fun(instance))
class MyModel(models.Model):
def __init__(self, *args, **kwargs):
super(MyModel).__init__(*args, **kwargs)
dynamic_add_checks(self, self._meta.get_field('some_choicefield')
some_choicefield = models.IntegerField(choices=SOME_CHOICES)
Now, this works but I have the feeling there is a better way to do it. Perhaps at class creation time (with metaclasses or in the new method)? Do you have any thoughts/suggestions about that?
Well I am not sure how to do this in your way, but in such cases I think the way to go is to simply create a new model, where you keep your choices, and change the field to ForeignKey. This is simpler to code and manage.
You can find a lot of information at a basic level in Django docs: Models: Relationships. In there, there are many links to follow expanding on various topics. Beyong that, I believe it just needs a bit of imagination, and maybe trial and error in the beginning.
I came across a similar problem where I needed to write large number of properties at runtime to provide backward compatibility while changing model fields. There are 2 standard ways to handle this -
First is to use a custom metaclass in your models, which inherits from models default metaclass.
Second, is to use class decorators. Class decorators sometimes provides an easy alternative to metaclasses, unless you have to do something before the creation of class, in which case you have to go with metaclasses.
I bet you know Django fields with choices provided will automatically have a display function.
Say you have a field defined like this:
category = models.SmallIntegerField(choices=CHOICES)
You can simply call a function called get_category_display() to access the display value. Here is the Django source code of this feature:
https://github.com/django/django/blob/baff4dd37dabfef1ff939513fa45124382b57bf8/django/db/models/base.py#L962
https://github.com/django/django/blob/baff4dd37dabfef1ff939513fa45124382b57bf8/django/db/models/fields/init.py#L704
So we can follow this approach to achieve our dynamically set property goal.
Here is my scenario, a little bit different from yours but down to the end it's the same:
I have two classes, Course and Lesson, class Lesson has a ForeignKey field of Course, and I want to add a property name cached_course to class Lesson which will try to get Course from cache first, and fallback to database if cache misses:
Here is a typical solution:
from django.db import models
class Course(models.Model):
# some fields
class Lesson(models.Model):
course = models.ForeignKey(Course)
#property
def cached_course(self):
key = key_func()
course = cache.get(key)
if not course:
course = get_model_from_db()
cache.set(key, course)
return course
Turns out I have so many ForeignKey fields to cache, so here is the code following the similar approach of Django get_FIELD_display feature:
from django.db import models
from django.utils.functional import curry
class CachedForeignKeyField(models.ForeignKey):
def contribute_to_class(self, cls, name, **kwargs):
super(models.ForeignKey, self).contribute_to_class(cls, name, **kwargs)
setattr(cls, "cached_%s" % self.name,
property(curry(cls._cached_FIELD, field=self)))
class BaseModel(models.Model):
def _cached_FIELD(self, field):
value = getattr(self, field.attname)
Model = field.related_model
return cache.get_model(Model, pk=value)
class Meta:
abstract = True
class Course(BaseModel):
# some fields
class Lesson(BaseModel):
course = CachedForeignKeyField(Course)
By customizing CachedForeignKeyField, and overwrite the contribute_to_class method, along with BaseModel class with a _cached_FIELD method, every CachedForeignKeyField will automatically have a cached_FIELD property accordingly.
Too good to be true, bravo!
I have one model that has a ManyToMany Field (let's call it "Options") with another Model
When I create the ModelForm it displays all options.
Is there any way to exclude some option values or to show only some of them?
Here is an example:
models.py
class Options (model.Models):
name = ...
...
class Anything (model.Models):
...
options = ManyToManyField(Options)
values of "Options" in my DB:
["OK",
"OK_2",
"NOT_OK",
"OK_3,
"NOT_OK_2"]
Let's say that I need to show ONLY the "OK" values and hide or not to show the "NOT_OK" values.
Is there any way to do this with ModelForms?
You certainly can filter the queryset for a foreign key field or m2m on the related model by using a Form or more commonly a ModelForm.
The reason doing this at form level is useful is because that filtering could well be based on business logic which is not applicable in all cases and so allows more flexibility than defining it against the model for example.
While you can do this while defining the form fields it is best to do it once the form has been constructed and so it takes place at runtime and not compile time (I have just experienced a few interesting occasions where this has caused me some issues, however that was an earlier version of Django!)
The following ModelForm would do the job:
class AnythingForm(ModelForm):
options = forms.MultipleChoiceField()
def __init__(self, **kwargs):
super(AnythingForm, self).__init__(self, **kwargs)
self.fields['options'].queryset = Option.objects.filter({pass in your filters here...})
class Meta:
model = Anything
You can pass the limit_choices_to parameter to your ManyToMany field:
from django.db.models import Q
class Anything (models.Model):
options = models.ManyToManyField(Options,
limit_choices_to=Q(name__startswith='OK'))
In django 1.7 you can even pass a callable in case if list of choices should be changed dynamically.
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)