I am trying to access data.get_age_display in my email template. I can't seem to get the display of this. I am not sure what I am doing wrong, I've using get_FIELD_display numerous times before but passed as context to a normal template. Is there something different with forms?
class RequestForm(forms.Form):
ADULT = 1
SENIOR = 2
STUDENT = 3
AGE_GROUP = (
(ADULT, 'Adult'),
(SENIOR, 'Senior'),
(STUDENT, 'Student'),
)
name = forms.CharField(max_length=255)
phone = forms.CharField(max_length=15)
age = forms.ChoiceField(choices=AGE_GROUP)
details = forms.CharField(widget=forms.Textarea())
def save(self):
order = Order(
name = self.cleaned_data['name'],
phone = self.cleaned_data['phone'],
age = self.cleaned_data['age'],
details = self.cleaned_data['details'],
)
order.save()
template = loader.get_template('request_email.txt')
# send over the order object in an email extracted so they can handle the ticket order
context = Context({
'data': order,
})
#import pdb; pdb.set_trace()
email_subject = 'Request Tickets'
mail_managers(email_subject, template.render(context))
in my request_email.txt all I am doing is {{ data.get_age_display }} any ideas?
Jeff
You haven't shown the code for the Order model that you're creating. Are you sure that the age field on the model has choices set?
Any reason you're not using a ModelForm? You're creating an Order object within the form's save() method, but not returning it. A modelform would do that for you, as well as removing the need to redeclare the fields for the form.
I know this is coming WAAAAAY later than the question being posted but here's my answer for completeness and anyone else who might benefit from it :-)
I'm going to assume that in AGE_GROUP, ADULT, SENIOR and STUDENT are integers. Your form cleaning will NOT automatically clean the string contained in the POST and return an integer. So in this code:
context = Context({
'data': order,
})
you would think order.age is referring to an integer but that is, in fact, incorrect. It's burned me a few times before because this will correctly save the integer to the physical table, but the order instance still has the string representation of the age field.
You could do one of two things:
1. Clean this in the field:
clean_age(self):
return int(self.cleaned_data['age'])
or create a new field type:
def MyChoiceField(forms.ChoiceField):
def clean(self, value):
if not value:
if self.required:
raise forms.ValidationError(self.error_messages['required'])
return None
else:
return None
return int(value)
link that to the form field:
age = MyChoiceField(choices=AGE_GROUP)
and then you'll be able to apply this logic to any other such choice field in future. Personally, I find the latter approach the best one and I stick all my custom field types into a form_utils file so that I can use them everywhere. Another gotcha is that forms.charField doesn't automatically strip the entered text and you can use this approach to fix that too.
Related
After searching for a while, I can't seem to find any answer for this so I'm asking this question. What I want to is display some values based on a person's salary and a bracket. My models looks like this.
class Employee
salary = models.DecimalField
class Benefit
some_fields
class EmployeeBenefit
employee = models.ForeignKey
benefit = models.ForeignKey
class BenefitVersion
benefit = models.ForeignKey
class BenefitBracket
benefit_version = models.ForeignKey
from_salary = models.DecimalField
to_salary = models.DecimalField
the_fields_I_want_to_display
As you can see it's quite deep, there would be a bunch of querying to do to get to the fields I want.
I basically need to get the employee's salary(which is easy because this is gonna be inside EmployeeAdmin) then get the current benefit of the EmployeeBenefit list item, then based on the benefit and the employee's salary, get the bracket and then display some of it's fields on the inline.
I want to display the the_fields_I_want_to_display on the admin.TabularInline for EmployeeBenefit inside my EmployeeAdmin. I was testing using a forms.ModelForm in the inline and modifying it's contents using get_form based on this answer but django is not calling get_form. I also previously tried using calculated_fields but it's not being rendered as well.
I'm using django 3.1.7 btw
Edit:
I found this and try it out right now. I think it has potential but unfortunately the obj that get_formset gives is Employee so I still need to find a way to get the right EmployeeBenefit for the list item
After some more searching, I already figured out a way to do this. Based on this SO answer, I got an idea on how I should receive the data I passed to the formset using get_formset.
So, in my get_formset, I did something like this.
def get_formset(self, request, obj=None, **kwargs):
formset = super(Inline, self).get_formset(request, obj, **kwargs)
formset.request = request
benefit_details_dict_arr = []
emp_contribs = models.EmployeeBenefit.objects.filter(employee=obj)
#because what I'm getting here is Employee and not a EmployeeBenefit, I have to query it first
for contrib in emp_contribs:
bracket = contrib.get_benefit_bracket(datetime.now())
benefit_details_dict_arr.append({
"key": bracket.val,
"key2": bracket.val2,
})
formset.benefit_details = benefit_details_dict_arr
return formset
Then on the formset, I receive the data this way.
class EmpBenefitFormSet(forms.models.BaseInlineFormSet):
model = models.EmployeeBenefit
def __init__(self, *args, **kwargs):
super(EmpBenefitFormSet, self).__init__(*args, **kwargs)
cntr = 0
for form in self.forms:
init_data = self.benefit_details[cntr]
for field in form.fields:
if field in init_data:
form.initial[field] = init_data[field]
form.fields[field].required = False
form.fields[field].disabled = True
cntr += 1
Also, if you're gonna use this way of receiving the data, each field you want to inflate should be declared on the fields of the inline. This should work if the fields you want to set an initial value are actual fields of the model. You might need to tweak this a bit to work for custom fields
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.
I'm currently working with django project. I had to filter the data store on the database based on the user input on form (at template) as looked below.
On form user either enter value or leave it blank. So what I have to do is first find the (valid) user input and then fire appropriate query to display data as user input in the form. So final result should be displayed on table at template.
As I'm new to django, how should I have to pass the data and fire query to represent data at multiple field. As help or link related to these type problem are expected. ( I just able to filter from the database with only one form and had no concept to solve this.)
Model of my temp project is as below.
class exReporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
gender = models.CharField(max_length=1)
age = models.IntegerField()
label = models.IntegerField()
There are a number of approaches you can take, but here is one solution you can use that involves chaining together the appropriate filters based on the form's posted data:
*Note: To conform to Python's naming convention, rename exReporter class to ExReporter.
# views.py
def process_ex_reporter_form(request):
if request.method == "POST":
# ExReporterForm implementation details not included.
ex_reporter_form = ExReporterForm(request.POST)
if ex_reporter_form.is_valid():
# If form field has no data, cleaned data should be None.
gender = ex_reporter_form.cleaned_data['gender']
age_start = ex_reporter_form.cleaned_data['age_start']
age_end = ex_reporter_form.cleaned_data['age_end']
aggregation_group = ex_reporter_form.cleaned_data['aggregation_group']
aggregation_id = ex_reporter_form.cleaned_data['aggregation_id']
ex_reporters = ExReporter.objects.get_ex_reporters(gender, age_start,
age_end, aggregation_group, aggregation_id)
else:
# Pass back form for correction.
pass
else:
# Pass new form to user.
pass
# models.py
class ExReporterManager(models.Manager):
def get_ex_reporters(self, gender, age_start, age_end, aggregation_group,
aggregation_id):
ex_reporters = super(ExReporterManager, self).get_query_set().all()
# Even though the filters are being applied in separate statements,
# database will only be hit once.
if ex_reporters:
if gender:
ex_reporters = ex_reporters.filter(gender=gender)
if age_start:
ex_reporters = ex_reporters.filter(age__gt=age_start)
if age_end:
ex_reporters = ex_reporters.filter(age__lt=age_end)
# Apply further filter logic for aggregation types supported.
return ex_reporters
I'm having strange behavior with a form ModelChoiceField. A little background. I need a form that has a variable queryset for a certain field. Looking at this question and this, I have created an init method for my form to handle this based on the request passed in to the init method.
class QueryTimeEntryForm(forms.Form):
query_start_date = forms.DateField(label='Start Date:', required=True, widget=forms.TextInput(), input_formats=['%m/%d/%Y', '%Y-%m-%d'])
query_end_date = forms.DateField(label='End Date:', required=True, widget=forms.TextInput(), input_formats=['%m/%d/%Y', '%Y-%m-%d'])
time_query_unit = forms.ModelChoiceField(queryset=Unit.objects.all().order_by('unit'), label='', required=False, empty_label='Choose a unit', widget=forms.Select())
time_query_employee = forms.ModelChoiceField(queryset=Employee.objects.none(), label='', required=False, empty_label='Choose an employee', widget=forms.Select())
time_query_radio = forms.ChoiceField(label='', widget=forms.RadioSelect(attrs={'class':'queryRadio'}), choices=QUERY_CHOICES, initial='1')
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super (QueryTimeEntryForm, self).__init__(*args, **kwargs)
#depending on the user, set the queryset of the employee drop down
#get the employee category for the user
today = datetime.today()
emp = Employee.objects.filter(user__exact=self.request.user)
ec = EmployeeCategory.objects.filter(employee__exact=emp[0]).filter(effectiveDate__lte=today).filter(Q(enddate__gte=today) | Q(enddate__isnull=True))[0]
if ec.category.category == 1:
self.fields['time_query_employee'].queryset = Employee.objects.filter(user__exact=self.request.user)
elif ec.category.category == 2:
#get the unit for this category 2 employee
unit = EmployeeUnit.objects.filter(employee__exact=emp).filter(effective_date__lte=today).filter(Q(end_date__gte=today) | Q(end_date__isnull=True))[0].unit
#get the employees who currently share the unit with the current category 2 employee, excluding the current category 2 employee
self.fields['time_query_employee'].queryset = Employee.objects.filter(employee_for_employeeunit__unit__exact=unit).filter(Q(employee_for_employeeunit__end_date__gte=today) | Q(employee_for_employeeunit__end_date__isnull=True)).exclude(employee_for_employeeunit__exact=emp).order_by('user__first_name')
else:
#get category 1
cat = Category.objects.filter(category__exact=1)[0]
self.fields['time_query_employee'].queryset = Employee.objects.filter(employee_for_employeecategory__category__exact=cat).filter(Q(employee_for_employeecategory__enddate__gte=today) | Q(employee_for_employeecategory__enddate__isnull=True)).order_by('user__first_name')
When the form isn't bound, everything works just fine. I get just the Employees I expect in the drop down in the html. The problem I'm having is that when posting the form, the ModelChoiceField fails validation. As I step through, I notice that similar to this question, I'm getting the "Select a valid choice" error, probably because the queryset is Employees.objects.none() when super is called and the validation occurs. Should I clear all the errors and redo a full_clean after setting the queryset, or should I take a different approach? Basically I'm stuck, not understanding exactly what's going on, nor where to go from here. Everything was working fine before I added the init method and had a standard Employee queryset, so it must be something I'm doing with that.
Please help. Thanks!
Have you been able to test all three of your category branches? Personally I would probably insert a pdb.set_trace() call at the beginning of the __init__ call, run it using the Django development server, and see what happens when I post the form.
As a readability tip - you can omit __exact, and you can replace your filter(Q() | Q()) calls here with a .exclude making the inverse comparison, since null values will never be true. That is, instead of your original:
unit = EmployeeUnit.objects.filter(employee__exact=emp).filter(effective_date__lte=today).filter(Q(end_date__gte=today) | Q(end_date__isnull=True))[0].unit
you can write:
unit = EmployeeUnit.objects.filter(employee=emp, effective_date__lte=today).exclude(end_date__lt=today)[0].unit
#Shawn, I encountered this same issue today. I noticed when debugging (in Eclipse, with the Variables pane shown/active) the form's __init__() method and line-stepping through the code that I'd get the "Select a valid choice" error. However, if I clear my breakpoints and just let it run, or if I line-step debug with the Variables pane not shown/not active, then I don't get the error. Something with the rendering of the variables in Eclipse results in the error.
I have a list of client records in my database. Every year, we generate a single work order for each client. Then, for each work order record, the user should be able to create a note that is specific to the work order. However, not all work orders need a note, just some.
Now, I can't simply add a note field to the work order because some times, we need to create the note before the work order is even generated. Sometimes this note is specific to a work order that won't happen for 2-3 years. Thus, the notes and the work order must be independent, although they will "find" each other when they both exist.
OK, so here's the situation. I want the user to be able to fill out a very simple note form, where they have two fields: noteYear and note. Thus, all they do is pick a year, and then write the note. The kicker is that the user should not be able to create two notes for the same year for the same client.
What I'm trying to get as is validating the note by ensuring that there isn't already a note for that year for that client. I'm assuming this would be achieved by a custom is_valid method within the form, but I can't figure out how to go about doing that.
This is what I tried so far (note that I know it's wrong, it doesn't work, but it's my attempt so far):
Note that systemID is my client record
My model:
class su_note(models.Model):
YEAR_CHOICES = (
('2013', 2013),
('2014', 2014),
('2015', 2015),
('2016', 2016),
('2017', 2017),
('2018', 2018),
('2019', 2019),
('2020', 2020),
('2021', 2021),
('2022', 2022),
('2023', 2023),
)
noteYear = models.CharField(choices = YEAR_CHOICES, max_length = 4, verbose_name = 'Relevant Year')
systemID = models.ForeignKey(System, verbose_name = 'System ID')
note = models.TextField(verbose_name = "Note")
def __unicode__(self):
return u'%s | %s | %s' % (self.systemID.systemID, self.noteYear, self.noteType)
And my form:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('noteYear', 'noteType', 'note')
def is_valid(self):
valid = super (SU_Note_Form, self).is_valid()
#If it is not valid, we're done -- send it back to the user to correct errors
if not valid:
return valid
# now to check that there is only one record of SU for the system
sysID = self.cleaned_data['systemID']
sysID = sysID.systemID
snotes = su_note.objects.filter(noteYear = self.cleaned_data['noteYear'])
for s in snotes:
if s.systemID == self.systemID:
self._errors['Validation_Error'] = 'There is already a startup note for this year'
return False
return True
EDIT -- Here's my solution (thanks to janos for sending me in the right direction)
My final form looks like this:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('systemID', 'noteYear', 'noteType', 'note')
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
s = su_note.objects.get(noteYear = cleaned_data['noteYear'], systemID__systemID = sysID)
print(s)
self.errors['noteYear'] = "There is already a note for this year."
except:
pass
return cleaned_data
For anyone else looking at this code, the only confusing part is the line that has: sysID = sysID.systemID. The systemID is actually a field of another model - even though systemID is also a field of this model -- poor design, probably, but it works.
See this page in the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/validation/
Since your validation logic depends on two fields (the year and the systemID), you need to implement this using a custom cleaning method on the form, for example:
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
su_note.objects.get(noteYear=cleaned_data['noteYear'], systemID=systemID)
raise forms.ValidationError('There is already a startup note for this year')
except su_note.DoesNotExist:
pass
# Always return the full collection of cleaned data.
return cleaned_data