Is there a way to modify the data obtained from the model before inserting it in the form?
Here's my model:
class SomeData(models.Model):
Some_No = models.ForeignKey('SomeDef', on_delete=models.PROTECT)
Some_Val = models.DecimalField(max_digits=10, decimal_places=3, verbose_name=_('Some_Val'))
And here's my form:
#autostrip
class SomeForm(forms.ModelForm):
class Meta:
model = models.SomeData
fields = ('Some_Val', 'Some_No')
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].label = _(' ')
def clean_some(self):
some = None
some_id = self.cleaned_data['some']
some = models.SomeDef.objects.get(Some_No=some_id)
return some
def save(self, something, *args, **kwargs):
orig_commit = kwargs.get('commit', True)
kwargs['commit'] = False
ri = super(SomeForm, self).save(*args, **kwargs)
ri.Some_No = something
if orig_commit:
try:
ri.save()
except ValidationError as e:
raise ValidationError
return ri
The data saved inside of the models is a bit different from what I want to show in the forms when these are populated with data. However, I cannot figure out how to do it in a smart way.
Using the pre_save signal. Signals
You can set the initial value in the __init__ method:
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].initial = f(self.instance)
You can pass an initial data dictionary argument to your form, when you instantiate it into your view.
Related
I am trying to make the 'cost_name' field choices to be filtered based on the dynamic project_id.
models.py
class ProjectCost(models.Model):
project_name = models.ForeignKey(ProjectName, on_delete=models.CASCADE,null=True)
cost_name = models.CharField('Cost Name', max_length=50)
total_budget = models.DecimalField('Total Budget', max_digits=9,decimal_places=2)
forms.py
class CreateCostForm(forms.ModelForm):
def __init__(self,project_id,*args, **kwargs):
super(CreateCostForm, self).__init__(*args, **kwargs)
self.fields['cost_name'].queryset = ProjectCost.objects.filter(project_name_id=project_id)
class meta:
model = ProjectCost
When i hard-code the value of project_id like:
self.fields['project_name'].queryset = ProjectCost.objects.filter(project_name_id=4) or
ProjectCost.objects.filter(project_name_id= 8),
i get the correct filtered options on the form.So how can i make project_id dynamic?
i tried:
def __init__(self, *args, **kwargs):
project_id = kwargs.pop('project_id', None)
super(CreateCostForm, self).__init__(*args, **kwargs)
self.fields['cost_name'].queryset = ProjectCost.objects.filter(project_name_id=project_id)
But this returns 'None' for the value of 'project_id'. Any idea on how to fix this?
Thanks.
As you are sub-classing from CreateView, then there is a method call get_form_kwargs() to send data from View to Form. Just override it like this:
class YourView(CreateView):
...
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(YourView, self).get_form_kwargs(*args, **kwargs)
form_kwargs['project_id'] = self.kwargs.get('project_id') # assuming you send the project_id through url ie path('project/<int:project_id>/create/', YourView.as_view())
return form_kwargs
In that way you will be get data in project_id in Form:
Class CreateCostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
project_id = kwargs.pop('project_id', None)
This is my model
class Userlist(models.Model):
username = models.CharField(max_length=20)
class Mailinglist(models.Model):
users = models.ManyToManyField(Userlist, blank=True)
def __init__(self, *args, **kwargs):
super(Mailinglist, self).__init__(*args, **kwargs)
userlist = Userlist.objects.all().filter(mailinglist__id=self.pk)
setattr(self, '__original_userlist', userlist)
def log(self):
userlist = Userlist.objects.filter(mailinglist__id=self.pk)
original = getattr(self, '__original_userlist')
print(userlist) #HERE1
print(original) #HERE2
def save(self, force_insert=False, force_update=False, *args, **kwargs):
super(Mailinglist, self).save(force_insert, force_update, *args, **kwargs)
self.log()
I'm trying to get the values before and after the user has been added or removed in my mailinglist, but the values are the same (the new values) in #HERE1 and #HERE2. Any thoughts how I can get the old value?
Ok, I fixed. Django only calls the queries once they are really needed. (Lazy query?)
What I did was to force the query.
I made the list as set
def __init__(self, *args, **kwargs):
super(Mailinglist, self).__init__(*args, **kwargs)
userlist = Userlist.objects.all().filter(mailinglist__id=self.pk)
setattr(self, '__original_userlist', set(userlist))
Then
def log(self):
userlist = Userlist.objects.filter(mailinglist__id=self.pk)
original = getattr(self, '__original_userlist')
print(set(userlist)) #HERE1
print(original) #HERE2
This way I could even compare with
diff = set_original - set_actual
Let's say we got two model:
model.py
class Engine(models.Model):
name = models.CharField(max_length=25)
class Car(models.Model):
name = models.CharField(max_length=25)
engine = models.OneToOneField(Engine)
engine_admin.py
class EngineAdmin(admin.ModelAdmin):
def get_changelist_form(self, request, **kwargs):
return EngineChangeListForm
form.py
class EngineChangeListForm(forms.ModelForm):
class Meta:
model = Engine
fields = []
# this work!
def save(self, *args, **kwargs):
car_obj = Car.objects.create(engine = self.instance)
return super(EngineChangeListForm, self).save(*args, **kwargs)
if I change save() to below, it would create a new car object, but in the end, engine object's car field will be empty.
I don't understand why the code below won't work.
# this won't work!
def save(self, *args, **kwargs):
car_obj = Car.objects.create(engine = self.instance)
self.instance.car = car_obj
return super(EngineChangeListForm, self).save(*args, **kwargs)
You need to call super() first to save the engine, before you create the car.
def save(self, *args, **kwargs):
instance = super(EngineChangeListForm, self).save(*args, **kwargs)
car_obj = Car.objects.create(engine=instance)
return instance
So, I have the following form:
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
My view:
d = Design.object.get(slug=fromInput)
....
DesignItemInlineFormSet = inlineformset_factory(Design, DesignItem, fk_name="design", form=DesignItemForm,)
if request.method == "POST":
formset = DesignItemInlineFormSet(request.POST, request.FILES, instance=d)
if formset.is_valid():
formset.save()
DesignItemInlineFormSet(instance=d)
As you can tell, in my form, I overwrote the quantity field to be a drop down instead of an integer field.
For some reason, when I submit the form, the data is not updated in the database. However, if I change the form to the following, it works (of course, it doesn't have the dropdowns I want, but it posts to the db). Why is this, and how do I fix it?
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
# CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
# self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
EDIT: Here is the DesignItem model:
class DesignItem(models.Model):
"""Specifies how many of an item are in a design."""
design = models.ForeignKey(Design, related_name="items")
quantity = models.PositiveIntegerField(default=1)
trackable = models.ForeignKey(Trackable, related_name="used")
have you tried just overriding the widget instead of the whole field?
i guess you want a select widget
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'].widget = forms.Select(choices=CHOICES)
explain me please how to use it in my Admin?
You can just create a custom ModelForm for your model, with the following:
remove_the_file = forms.BooleanField(required=False)
def save(self, *args, **kwargs):
object = super(self.__class__, self).save(*args, **kwargs)
if self.cleaned_data.get('remove_the_file'):
object.the_file = ''
return object
Use that form in your ModelAdmin, and there's no need to change the database.
there is what i created in forms.py:
class MediaForm(forms.ModelForm):
remove_the_file = forms.BooleanField(required=False)
def save(self, *args, **kwargs):
object = super(self.__class__, self).save(*args, **kwargs)
if self.cleaned_data.get('remove_the_file'):
object.the_file = ''
return object
And there is my admin.py:
class MediaAdmin(admin.ModelAdmin):
raw_id_fields = ('parent',)
how should i change MediaAdmin class to apply it?
class MediaAdmin(admin.ModelAdmin):
raw_id_fields = ('parent',)
form = MediaForm