Django, ModelChoiceField() and initial value - django

I'm using something like this:
field1 = forms.ModelChoiceField(queryset=...)
How can I make my form show the a value as selected?

If you want to set the default initial value you should be defining initial like other form fields except you set it to the id instead.
Say you've got field1 like this:
class YourForm(forms.Form):
field1 = forms.ModelChoiceField(queryset = MyModel.objects.all() )
then you need to set initial when you create your form like this:
form = YourForm(initial = {'field1': instance_of_mymodel.pk })
rather than:
form = YourForm(initial = {'field1': instance_of_mymodel })
I'm also assuming you've defined __unicode__ for your models so this displays correctly.

You can just use
field1 = forms.ModelChoiceField(queryset=..., initial=0)
to make the first value selected etc. It's more generic way, then the other answer.

The times they have changed:
The default initial value can now be set by defining initial like other form fields except you set it to the id instead.
Now this will suffice:
form = YourForm(initial = {'field1': instance_of_mymodel })
Though both still work.

The code
form = YourForm(initial = {'field1': instance_of_mymodel.pk })
and
form = YourForm(initial = {'field1': instance_of_mymodel })
or initial field directly following:
field1 = forms.ModelChoiceField(queryset=..., initial=0)
All work.
The first two ways will override the final way.

field1 = forms.ModelChoiceField(queryset=Model.objects.all(), empty_label="Selected value")
It's as simple as that....!

Just want to add this answer after stumbling on this question. I know it works on Django 3.2, at least.
If you have some calculated value in the __init__ method, you can do this to set the initial value at instantiation as well:
def __init__(self, value, *args, **kwargs):
# super call, etc.
self.do_something(value)
self.fields['field'].initial = value
If the form does multiple things with value, it's a bit more DRY to pass it only once instead of redundantly with the initial kwarg in instantiation.

You could do this as well:
form = YourForm(initial = {'field1': pk })
if you are parsing your primary key through a query string or via an ajax call no need for an instance, the query set has already handled that for your drop down, the pk indexes the state you want

Related

(Flask) WTForm gets rendered with wrong values

I am currently working on a web-application based "database-explorer" for a university project.
Basically I create a site for each relation of the database, where the user can view the data and additionally I want them to be able to add data.
For that I use wtforms. I take the inputs, create a simple "SQL string" with it and execute it.
To make the input easier, I want to use dynamic SelectFields.
This is my approute:
#app.route('/table/fact_angestellte')
def fact_angestellte():
if current_user.is_authenticated:
mycursor.execute("SELECT * FROM dim_rolle WHERE ro_rolle !='Ansprechpartner' AND ro_rolle != 'Teilnehmer';")
choicesRolle = mycursor.fetchall()
form = InsertAngestelltenForm(choicesRolle)
print(form.choicesRolle)
mycursor.execute("SELECT * FROM fact_angestellte INNER JOIN dim_rolle ON fact_angestellte.an_rolle_fk = dim_rolle.ro_id_pk;")
data = mycursor.fetchall()
return render_template('tables/fact_angestellte.html', data=data, form=form)
else:
return redirect(url_for('login'))
The form gets created successfully and if I try to print form.choicesRolle (in the approute), it also gives me the correct output in the console. But when I go on my website, the SelectField still has the default value of choicesRolle.
class InsertAngestelltenForm(FlaskForm):
choicesRolle =[]
nachname = StringField('Nachname', validators=[DataRequired()])
vorname = StringField('Vorname',validators=[DataRequired()])
geschlecht = SelectField('Geschlecht', choices=[('maennlich', 'männlich'), ('weiblich', 'weiblich')], validators=[DataRequired()])
postleitzahl = StringField('Postleitzahl | FK', validators=[DataRequired()])
strasse = StringField('Straße und Nummer', validators=[DataRequired()])
rolle = SelectField('Rolle', choices=choicesRolle, validators=[DataRequired()])
submit = SubmitField('Eintrag hinzufügen')
def __init__(self, choicesRolle):
super().__init__()
self.choicesRolle = choicesRolle
print(self.choicesRolle)
So my problem is: the object has the correct attributes, but somehow they don't "reach" the template.
Any help is appreciated.
Greetings
Per the WTForms documentation, the choices keyword is only evaluated once. In your example, this means it's evaluating to the empty array you set in the choicesRolle class attribute, and that's what's being passed to Flask. You need to set the choices after the form is instantiated, not during it.
On your form class, remove the entire __init__ method, the choicesRolle class attribute, and the choices parameter from the rolle SelectField. Then, in your fact_angestellte view function, set the form's choices after you instantiate it, as follows:
choicesRolle = mycursor.fetchall()
form = InsertAngestelltenForm()
form.rolle.choices = choicesRolle
This should work...let me know. Note that I'm not sure what data is being returned from mycursor.fetchall() as you don't really describe, but the SelectField choices needs to be a list of values...WTForms by default coerces each value to unicode.

How to determine if a field has changed in a Django modelform

I was surprised that this was difficult to do. However I came up with this, which seems to work at least for my simple case. Can anyone recommend a better approach?
def field_changed(self, fieldname):
"""Tests if the value of the field changed from the original data"""
orig_value = self.fields[fieldname].initial or getattr(self.instance, field, None)
orig_value = getattr(orig_value, 'pk', orig_value)
if type(orig_value) is bool:
# because None and False can be interchangeable
return bool(self.data.get(fieldname)) != bool(orig_value)
else:
return unicode(self.data.get(fieldname)) != unicode(orig_value)
Form contains a property changed_data which holds a list of all the fields whose values have changed.
Try:
'fieldname' in myforminstance.changed_data
It seems that you have reinvented .has_changed() method.
Extending on Airs's answer, for multiple fields:
In a scenario where you'd like to track changes for a list of fields ['field_a', 'field_b', 'field_c']
If you'd like to check if any of those fields has changed:
any(x in myforminstance.changed_data for x in ['field_a', 'field_b', 'field_c'])
If you'd like to check if all of those fields have changed:
all(x in myforminstance.changed_data for x in ['field_a', 'field_b', 'field_c'])
You must override the method post_save. Overriding methods is a good practice in Django: https://docs.djangoproject.com/en/dev/ref/signals/#post-save
I suggest you adding something like this into your model class:
def post_save(self, sender, instance, created, raw, using, update_fields):
if 'the_field' in update_fields:
# Do whatever...

Django ModelChoiceField: filtering query set and setting default value as an object

I have a Django Form class defined likes this in Models:
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
This works OK, but it has some limitations I can't seem to work around:
(1) I would like to use a filter on the queryset, based on a variable accountid passed to the form, like this:
User.objects.filter(account=accountid)
This can't work in the model because accountid can't be passed as a variable, of course.
It follows that the queryset must somehow be defined in the Views, but as far as I can see it's a required field in the Form class.
(2) I would like to make the default choice of AccountDetailsForm an object in the database, which I can select in the Views like this:
User.objects.filter(account=accountid).filter(primary_user=1)
I've tried specifying the adminuser as a default value in the form, (which works with other standard form fields, like CharField):
adminuser = User.objects.filter(account=accountid).filter(primary_user=1)
...
form = AccountDetailsForm({'adminuser': adminuser})
return render_to_response('accounts/edit/accountdetails.html',
{'form': form, 'account':account})
But no luck.
Should I be using something other than ModelChoiceField given the flexibility I need here?
Thanks.
Override the init method and accept a new keyword argument
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
def __init__(self, *args, **kwargs):
accountid = kwargs.pop('accountid', None)
super(AccountDetailsForm, self).__init__(*args, **kwargs)
if accountid:
self.fields['adminuser'].queryset = User.objects.filter(account=accountid)
form = AccountDetailsForm(accountid=3)
You can always just set the choices manually in the view as well.
form = AccountDetailsForm()
form.fields['adminuser'].queryset = User.objects.filter(account=accountid)
Be warned: you are not setting default values by passing in a dictionary to a form like in your example.
You are actually creating a Bound Form, potentially triggering validation and all that jazz.
To set defaults, use the initials argument.
form = AccountDetailsForm(initial={'adminuser':'3'})
You can override the field in the view
yourForm = AccountDetailsForm()
yourForm.fields['accomodation'] = forms.ModelChoiceField(User.objects.filter(account=accountid).filter(primary_user=1))
Something that hasn't been mentioned here yet is the Form.clean() method. This method is specifically for custom validation.
For your example, you could do something like this:
class AccountDetailsForm(forms.Form):
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
account_id = forms.IntegerField() # or ModelChoiceField if that applies
def clean(self):
account_id = self.cleaned_data['account_id']
self.cleaned_data['adminuser'] = User.objects.filter(account_id=account_id)
return self.cleaned_data
The clean() method gets called after the default clean methods, so you can use self.cleaned_data (same as form.cleaned_data in the view) and return it however you'd like.
Even better, you can name the method according to the field you'd like to clean (def clean_adminuser) and make easier to read.
def clean_adminuser(self):
account_id = self.cleaned_data['account_id']
return User.objects.filter(account_id=account_id)
Also in this method you can call Form.add_error() if there are any issues you want to handle.
In Django 2.0 you can pass object (User in your case) from the view to the form like this (you have to retrieve obj from the DB first):
form = AccountDetailsForm(initial={'adminuser': adminuser})
It will give you a default selected object (answers your 2) question)

django request.POST field name substitution

I am using ajax to send data into a django view with data coming in via request.POST. I am posting the model field that needs to be updated as well as the model value. I just need to know how to use the field name variable I extract from request.POST['field_name'] so I can set the field in the model. Here is my code.
field_name = request.POST["field_name"]
field_value = request.POST["field_value"]
member_id = get_member_session(request).id
try:
member = Members.objects.get(id=member_id)
except:
status="ERROR-USER-DOES-NOT-EXIST"
return json_status(status)
try:
member.field_name=field_value
member.save()
return json_status('OK')
except:
status = "USER_SAVE_ERROR"
return json_status(status)
member.field_name is obviously not right. Do I need to use eval(field_name) or something like that? I would prefer not to if possible.
Many thanks
Rich
Use setattr, which allows you to set a variable attribute on an object:
try:
member._meta.get_field(field_name)
except member.FieldDoesNotExist:
# return something to indicate the field doesn't exist
return json_status('USER_FIELD_ERROR')
setattr(member, field_name, field_value)
member.save()
return json_status('OK')
edit: I updated to use model._meta.get_field, as it's a better approach. Mentioned in this answer for another question.

Trying to Nullify Django model fields with method where model and fields are parameters

I'm trying to write a method like the below where a list of fields (a subset of all the fields) is passed in as a parameter and has their column values set to null. I would be happy of I could get a method with just the fields as a parameter like below, but having the model as a parameter would be even better.
from my_project.my_app.models import MyModel
def nullify_columns (self, null_fields):
field_names = MyModel._meta.get_all_field_names()
for field in field_names:
if field in null_fields:
# The below line does not work because I'm not sure how to
# dynamically assign the field name.
MyModel.objects.all().update( (MyModel.get_field(field).column) = None)
Right now I have something like
if 'column1' in list_of_fields:
MyModel.objects.all().update(column1 = None)
if 'column2' in list_of_fields:
MyModel.objects.all().update(column2 = None)
etc. which is horrible, but works.
It's in the tutorial:
MyModel.objects.all().update(**dict.fromkeys(null_fields))