Modify UpdateView Form Data Before Presenting To User - django

I'm using a CreateView and UpdateView for managing saving and updating. Before my data is saved I need combine 3 form fields into one field for storing it in my model. Basically, I'm taking a longitude, latitude, and range and converting it to a single value that is stored in my database. In my ModelForm I create the extra fields that I need and remove the one field that I don't:
class FilterForm(ModelForm):
lat = forms.FloatField()
lgt = forms.FloatField()
range = forms.FloatField()
class Meta:
model = AdFilter
fields = ['title', 'tags', 'start_date', 'end_date', 'start_time', 'end_time', 'week_days', 'ad']
To create new I implement form_valid() in order to combine the longitude, latitude, and range and store it in my model correctly:
class FilterCreate(CreateView):
form_class = FilterForm
template_name = 'filter_form.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(FilterCreate, self).dispatch(*args, **kwargs)
def form_valid(self, form):
new_filter = form.save(commit=False)
new_filter.creator = self.request.user
utm_coordinates = utm.from_latlon(float(form.data['lat']), float(form.data['lgt']))
center = geos.Point(utm_coordinates[0], utm_coordinates[1])
broadcast_area_geometry = center.buffer(float(form.data['range']))
# Set the right SRID
utm_epsg = int('326' + str(utm_coordinates[2]))
broadcast_area_geometry.srid = utm_epsg
new_filter.filter_geometry = broadcast_area_geometry
new_filter.save()
return super(FilterCreate, self).form_valid(new_filter)
This all works fine. Now I'm trying to do the opposite of what form_valid() does in my UpdateView for the situation when someone gets the form. So I need to go from my single model value and create a longitude, latitude, and range values. I tried doing this inside get_context_data() but I've only been able to figure out how to add fields and not how to modify existing ones.
This seems like a very common problem but I can't seem to find an example of how to implement this. Maybe I'm looking in the wrong place.

You probably want to override the get_initial method to provide default values. The method should return a dictionary mapping field names to their default values.

Related

how to have a django form validator run on each object with ModelMultipleChoiceField rather than the queryset as a whole

I have a modelform which generates a series of checkboxes to add children to a parent part. I have created a validator that checks for cyclic relationships. I'm currently getting errors however because "proposed_child" is a queryset containing however many values the user has selected. How do I have this validator run on each object in that queryset?
def __init__(self, qs, part, *args, **kwargs):
super(AddPartToAssemblyForm, self).__init__(*args, **kwargs)
self.part = part
self.fields['children'] = forms.ModelMultipleChoiceField(
queryset=qs,
widget=forms.CheckboxSelectMultiple
)
def clean_children(self):
proposed_child = self.cleaned_data['children']
part = self.part
validate_acyclic_relationship(part, proposed_child)
class Meta:
model = Part
fields = ['children']
I figured it out, I changed proposed_child to proposed children and changed my validator so that it iterates through the queryset it receives and validates each object in turn.
Now I have working validator but if the data is valid the form isn't returning anything in form.cleaned_data['children']
--edit--
needed to return proposed_children at the end of clean_children

add extra field to ModelForm

I am adding an extra field to a Django ModelForm like that:
class form(forms.ModelForm):
extra_field = forms.CharField(label='Name of Institution')
class Meta:
model = db_institutionInstitution
fields = ['conn_kind','time','inst_name2']
The form is actually working fine, but I cant prepopulate it. I use it in a modelformset_factory:
formset = modelformset_factory(db_institutionInstitution,form=form)
I manually run through a queryset and add the entry in the dictionary needed for the additional form in the formset. However, when I call:
formset1 = formset(prefix='brch',queryset=qs1)
the extra_field is not prepopulated as intended (the rest is working fine).
Can anyone help?
If you want to set a default.
extra_field = forms.CharField(label='Name of Institution', initial="harvard")
If you want to dynamically set a value put it on form initialization:
def __init__(self, *args, **kwargs):
super(form, self).__init__(*args, **kwargs)
self.fields['extra_field'].initial = "harvard"

Django: Different form field widget per form in formset

I'm trying to find best approach for what i want. And i could use some help for that.
I have Model A and Model B. Model B has
modela = forms.ForeignKey(Model a)
I want to create a view where you can edit both single Model A and several Model B's on same page. Django has formsets for this and they work great.
I have one detail though, that messes things up tiny bit. Namely - i want the widgets or model B fields to be different based on what choices they have done in previous fields of same object. Because - based on type, the widget has to be datetime picker input or plain textinput.
Model B looks like this:
class ModelB(models.Model):
m0odela = models.ForeignKey(ModelA)
target_value = models.CharField()
target_type = models.CharField( choices = ( there are choices))
target_threshold = models.CharField()
I know i can provide my own form for formset and i could do this widget assignment in that form.
But the problem is, that when formset has no instances/queryset then i cant check if 'target_type' has been set for forms instance. So i would have to do it based on self.data or self.initial in form. But self.initial is also not present in form.__init__(). What i can work with is self.data - but that is raw request.POST or request.GET data - which contains all keys like 'mymodelb_set-0-target_type'.
So i'm bit lost here. Do i have to do some key parsing and figure out which -target_type belongs to current form and get chosen value there and assign widgets based on this value?
Or do i have to create my own subclass of BaseInlineFormSet and override _construc_form there somehow? So that form would have initial key with related data in **kwargs.
Has someone ran into this kind of problem before?
Alan
Well i had to solve it so i solved it as good/bad i could.
I created my own subclass of inline formset:
class MyInlineFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
initial = {}
fname = '%s-%s-%s' % (self.prefix, i, 'important_field_name')
initial['target_type'] = self.data[fname] if fname in self.data.keys() else 'km'
kwargs.update({'initial':initial})
form = super(MyInlineFormSet, self)._construct_form(i, **kwargs)
return form
And then in the form class:
class MyNiftyForm(forms.ModelForm):
class Meta:
model = MyAwesomeObject
fields=('field_one', 'field_two', 'field_three')
def __init__(self, *args, **kwargs):
super(ServiceTargetForm, self).__init__(*args, **kwargs)
if self.instance:
if self.instance.field_one == 'date':
self.fields['field_one'].widget.attrs['class'] = 'datepicker'
if self.initial:
if self.initial['field_one'] == 'date':
self.fields['field_one'].widget.attrs['class'] = 'datepicker'
and then in view:
MySuperCoolFormSet = inlineformset_factory(ImportantObject, MyAwesomeObject, extra = 1, form = MyNiftyForm, formset = MyInlineFormSet)
And it works.
Alan

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...

Django Forms: Hidden model field?

I've got a Form. I want to include a hidden field that returns a model. I'll set it's value in the view; I just need it to be posted along to the next page.
What field am I supposed to use in the form class?
A hidden field that returns a model? So a model instance ID?
The forms.HiddenInput widget should do the trick, whether on a FK field or CharField you put a model instance ID in.
class MyForm(forms.Form):
hidden_2 = forms.CharField(widget=forms.HiddenInput())
hidden_css = forms.CharField(widget=forms.MostWidgets(attrs={'style': 'display:none;'}))
I suppose the fastest way to get this working is
class MyForm(forms.Form):
model_instance = forms.ModelChoiceField(queryset=MyModel.objects.all(), widget=forms.HiddenInput())
form = MyForm({'model_instance': '1'})
form.cleaned_data['model_instance']
But I don't like the idea of supplying MyModel.objects.all() if you're going to specify one item anyways.
It seems like to avoid that behavior, you'd have to override the form __init__ with a smaller QuerySet.
I think I prefer the old fashioned way:
class MyForm(forms.Form):
model_instance = forms.CharField(widget=forms.HiddenInput())
def clean_model_instance(self):
data = self.cleaned_data['model_instance']
if not data:
raise forms.ValidationError()
try:
instance = MyModel.objects.get(id=data)
except MyModel.DoesNotExist:
raise forms.ValidationError()
return instance
The approach in Yuji's answer uses a clean_model_instance method on the form which is fine if you're only ever doing this once in your code base. If you do it more often, then you might benefit from implementing a custom model field.
This is the code I have:
from django import forms
class ModelField(forms.Field):
Model = None
def prepare_value(self, value):
"""Inject entities' id value into the form's html data"""
if isinstance(value, self.Model):
return value.id
return value
def to_python(self, value):
"""More or less stolen from ModelChoiceField.to_python"""
if value in self.empty_values:
return None
try:
value = self.Model.objects.get(id=value)
except (ValueError, self.Model.DoesNotExist):
raise forms.ValidationError('%s does not exist'
% self.Model.__class__.__name__.capitalize())
return value
If you use that as a base class and then specialise it with your own models then it becomes a useful based. For example:
# In app/fields.py
from .models import CustomModel
class CustomModelField(ModelField):
Model = CustomModel
Then you can pair that with whatever widget you need at the time:
# in app/forms.py
class MyForm(forms.Form):
hidden_custom_model_field = CustomModelField(widget=forms.HiddenInput())
other_widget_custom_model_field = CustomModelField(widget=MyCustomWidget())