Django clean_fields and mandatory field - python

In my models.py I have a field:
content_type = models.CharField(u"Content type", max_length=20)
As you can see it is mandatory. But I want to assign it a value automatically, so I did:
def clean_fields(self, *args, **kwargs):
print(kwargs)
self.content_type = "Whatever"
But in the admin of Django I get a validation error telling me that this field is required. However when I look at the content of kwargs, I get:
{'exclude': ['id', 'created_on', 'modified_on', 'content_type', 'content_ptr']}
So my questions are:
1) Why does this field appear in the 'exclude' list?
2) Why does Django raises this validation error?

You can assign a value automatically to your model like this.
class ModelName(models.Model)
content_type = models.CharField(u"Content type", max_length=20)
def save(self, *args, **kwargs):
if not self.pk:
self.set_content_type()
super(ModelName, self).save(*args, **kwargs)
def set_content_type(self):
self.content_type = 'Whatever'
self.save()
In this way whenever an instance of that model will be created, that field will automatically get value.
clean_field is used to validate form fields.

Related

Django pass field from serializer to model.save() that is not present in the model

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)

Pass request.user parameter to modelformset_factory form

So I've a formset tied to a model and one of the fields in that is ForeignKey.
models.py
class Squad(models.Model):
rid = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
def __str__(self):
return self.team.tname
forms.py
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, logged_user, user, *args, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
As you can see, the __init__ function is expecting an extra parameter logged_user which I'm hoping to pass via the views.py file. But if I do the following:
views.py
def choose_teams(request):
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm(request.user))
form = teamformset(queryset=Squad.objects.none())
return render(request, 'foo.html', {'form':form})
I'm trying to pass the logged in user as a parameter on line 2 but this is resulting in the following message:
Field 'id' expected a number but got 'SquadForm'
Not sure what I'm missing here. But if I remove the parameter from line 2:
teamformset = modelformset_factory(Squad, extra=series.team_number, form=SquadForm)
it starts working (of course, I no longer expect the user in the forms.py file and remove it too) but shows all the data and not filtered one.
You can pass additional keyword arguments to your formset form by passing form_kwargs={} to your formset
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, *args, logged_user, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm)
form = teamformset(queryset=Squad.objects.none(), form_kwargs={'logged_user': request.user})

Django Form field initial value when updating an instance

I have a custom Django ModelForm that I use to update a model instance.
This is the example model:
class MyModel(models.Model):
number = models.CharField(_("Number"), max_length=30, unique=True)
sent_date = models.DateField(_('Sent date'), null=True, blank=True)
When creating an instance I will pass only the number field, that is why I don't want the sent_date to be required.
Then I have a view that updates the sent_date field, using this custom form:
# Generic form updater
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
# Set initial values
if hasattr(self, 'initial_values'):
for field_name, value in self.initial_values.items():
self.initial[field_name] = value
class SentForm(MyModelUpdateForm):
required_fields = ['sent_date']
initial_values = {'sent_date': datetime.date.today()}
class Meta(MyModelUpdateForm.Meta):
fields = ['sent_date']
field_classes = {'sent_date': MyCustomDateField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MyModelUpdateForm is a generic ancestor for concrete forms like SentForm.
In my view whenever there is a GET I manually instantiate the form with:
my_form = SentForm({instance: my_model_instance})
So in this case I would expect the sent_date field to have an initial value set to today's date even tough the real model instance field is None.
If I inspect my_form object it does indeed have these attributes:
initial: {'sent_date': datetime.date(2018, 3, 1)}
instance: my_model_instance
fields: {'sent_date':
...: ...,
'initial': None # Why this is None?
...: ...
}
So apparently it should work but it doesn't: the field is always empty.
So I suspect that the value is coming from my_model_instance.sent_date that is in fact None.
The initial['sent_date'] = datetime.date(2018, 3, 1) is correct.
On the other side fields['sent_date']['initial'] = None it's not.
How can I always show the initial value when my_model_instance.sent_date is None?
Apparently I've solved with:
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
if hasattr(self, 'initial_values') and not kwargs.get('data'):
for field_name, value in self.initial_values.items():
if not getattr(kwargs.get('instance', None), field_name, None):
initial[field_name] = value
kwargs.update({'initial': initial})
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
Even tough it works I wouldn't mind a less hackish solution if anyone has any :)
I have this case in many places in my app without having any problem. However, I use a different way to set up initial value of some fields of an existing instance. Instead of:
self.initial[field_name] = value
I write, after having called super():
self.fields[field_name].initial = value
Can you try and tell the result ?

How to solve ValueError When save ModelMultipleChoiceField in ModelForm?

My model.py:
class RelayAddress(models.Model):
id = models.AutoField(primary_key=True,default=0)
sister_relay_relation = models.ManyToManyField('self', through='RelaySisterRelation',symmetrical=False)
def save(self, *args, **kwargs):
self.update_time = int(time.time())
super(RelayAddress,self).save(*args, **kwargs)
class RelaySisterRelation(models.Model):
id = models.AutoField(primary_key=True,default=0)
relay = models.ForeignKey(RelayAddress,related_name="relay")
sister_relay = models.ForeignKey(RelayAddress,related_name="sister_relay")
My admin.py
class RelaySisterRelationForm(forms.ModelForm):
relay=forms.ModelMultipleChoiceField(label=u'relay',widget=forms.CheckboxSelectMultiple(),queryset=RelayAddress.objects.all())
sister_relay=forms.ModelMultipleChoiceField(label=u'sister_relay',widget=forms.CheckboxSelectMultiple(),queryset=RelayAddress.objects.all())
def save(self, *args, **kwargs):
return super(RelaySisterRelationForm, self).save(*args,**kwargs)
And my view.py is null, then I get a ValueError:
Cannot assign "[<RelayAddress: RelayAddress object>]": "RelaySisterRelation.relay" must be a "RelayAddress" instance.
And how to solve this problem.
RelaySisterRelation.relay is a ForeignKey to RelayAddress meaning it can only store a references to one RelayAddress but your RelaySisterRelationForm.relay uses ModelMultipleChoiceField which is for many-to-many relations so returns a (potentially empty) list of RelayAddress instances.

Store form fields as key-values / individual rows

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.

Categories

Resources