I have two basic models:
class ModelA(models.model):
... #some fields
score = models.IntegerField()
class ModelB(models.model)
... #some fields
related_model=models.OneToOneField(ModelA)
score = models.IntegerField(default=related_model.score)
What I want is that upon creation of ModelB, it's score field be filled with the value of score of ModelA to which it has a OneToOne relation.
I have tried setting the score = models.IntegerField(default=related_model.score) but upon migration I get the error:AttributeError: 'OneToOneField' object has no attribute 'score'
I also tried defining a method under ModelB as follows and passing it to the default:
def get_score(self, *args, **kwargs):
return self.threat.score
This doesn't work either.
when I set default=get_score() I get the error: missing one required positional argument: self
How can I automatically set a model's field to be a field of it's related model's (by OneToOne Relation) field?
You should do this on save.
def save(self, *args, **kwargs):
if not self.score:
self.score = self.threat.score
return super().save(*args, **kwargs)
Related
I need to pass fields that are present in serializer, but not present in model to model save method (I have complicated saving logic and I want to make some decisions in object creation based on these fields). How can I do that? I tried to add
non_db_field = property to model, but I still get error MyModel() got an unexpected keyword argument 'negative_amount'
Let's say my model is
class MyModel(AbstractModel):
field1 = models.DateTimeField()
field2 = models.BigIntegerField()
My serializer is
class MyModelSerializer(AbstractSerializer):
field3 = serializers.BooleanField(required=False)
class Meta(AbstractSerializer.Meta):
model = MyModel
fields = '__all__'
And my viewset is
class MyModelViewSet(AbstractViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
You should handle this behavior in serializer.save method, for example, you can pop it from validated_data like that:
def save(self, **kwargs):
self.validated_data.pop("negative_amount")
return super().save(**kwargs)
You can use fields=['field1', 'field2', 'field3'] in serializer instead of fields='__all__'.
I found a solution based partly on Sharpek's answer and partly based on this answer:
In serializer I override save method:
def save(self, **kwargs):
if 'field3' in self.validated_data:
kwargs['field3'] = self.validated_data.pop('field3')
return super().save(**kwargs)
In models I override init method and define field:
field3 = None
def __init__(self, *args, **kwargs):
if 'field3' in kwargs:
self.field3 = kwargs.pop('field3')
super(Reading, self).__init__(*args, **kwargs)
I would like to have a form with the preselected checkboxes of a ManyToManyField.
models.py
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
forms.py
class StoreForm(ModelForm):
class Meta:
model = Store
fields = ('brands',)
I get this exception:
django.core.exceptions.FieldError: Unknown field(s) (brands) specified for Store
I know that I can add the field manually to the class:
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
If I do this the checkboxes are not preselected.
How is it possible to include the ManyToMany field from "the other side" of the model (from Store)?
#hedgie To change the field in the other model is not a good option for me because I use it already.
But the __init__() was a good hint. I come up with this solution and it seems to work.
class StoreForm(ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
brand_ids = [t.pk for t in kwargs['instance'].brands.all()]
kwargs['initial'] = {
'brands': brand_ids,
}
super().__init__(*args, **kwargs)
# https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = forms.ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.brands.clear()
for brand in self.cleaned_data['brands']:
instance.brands.add(brand)
self.save_m2m = save_m2m
# Do we need to save all changes now?
# Just like this
# if commit:
instance.save()
self.save_m2m()
return instance
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
Though it seems to be not very elegant. I wonder why django does not support a better way.
One possibility is to define the field on the "other" model. So instead of writing this:
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
You can write this:
class Brand(models.Model):
...
class Store(models.Model):
brands = models.ManyToManyField(Brand, blank=True, related_name="stores")
Or, if you have manually added the field to the form, you could populate its initial value in the form's __init__() method.
i am trying to set the min_value Attribute on Form level within my modelform.
Forms.py
class ProductForm(forms.models.ModelForm):
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Model.py
class Artikel(models.Model):
price = models.FloatField(help_text ='Price')
How can i setup the modelform that i can constrain the values allowed on the modelform?
I want the user to only enter values greater than or equal to 0.01.
I dont want to restricted on Database Level cause i dont want to limit myself in that regard.
You can override the ModelForm's init method. This will set the min attribute on the field to 10:
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 10
In addition to setting 'min' attribute on widget, also override form's clean_fieldname() method:
class ProductForm(forms.models.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 0.01
def clean_price(self):
price = self.cleaned_data['price']
if price < 0.01:
raise forms.ValidationError("Price cannot be less than 0.01")
return price
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Doc says:
The clean_<fieldname>() method is called on a form subclass – where is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
The simple way to do this is to set the validator on the field and provide a custom error message:
class ProductModelForm(forms.ModelForm):
price = forms.FloatField(min_value=0.01,
error_messages={'min_value': u'Price cannot be less than 0.01'})
I'm using admin.TabularInline in my admin code for which I've made a custom form.
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
class RateCardInline(admin.TabularInline):
model = models.RateCard
form = RateCardForm
extra = 3
The problem is that after I've saved my model instance, whenever I edit the model instance, it would remove the pre-selected choice and I'll have to select the choice again. Any ideas as to how to stop it?
Also for ModelChoiceField if I don't specify the label, then it would come up as None on admin page, but I don't need to specify it for admin.StackedInline.
To preselect the currently selected category instance you can set its primary key to the field's initial value by overriding __init__() on the ModelForm:
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
def __init__(self, *args, **kwargs):
super(RateCardForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
# Instance will be None for the empty extra rows.
if instance:
selected_pk = # query the primary key of the currently selected category here
self.fields['category'].initial = selected_pk
I have a simple form in Django that looks like this:
class SettingForm(forms.Form):
theme = forms.CharField(rrequired=True,
initial='multgi'
)
defaultinputmessage = forms.CharField(required=True,
initial='Type here to begin..'
)
...and the model to store it looks like:
class Setting(models.Model):
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
When the form is submitted, how can i store the form fields as key value pairs and then when the page is rendered, how can I initialize the form with the key's value. I've tried looking for an implementation of this but have been unable to find one.
Any help?
Thanks.
I'm assuming you want to store 'theme' as the name and the value as the value, same for defaultinputmessage. If that's the case, this should work:
form = SettingForm({'theme': 'sometheme', 'defaultinputmessage': 'hello'})
if form.is_valid():
for key in form.fields.keys():
setting = Setting.objects.create(name=key, value=form.cleaned_data[key])
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.