Using form data in a model method

I have a model method:

class Member(models.Model):

    ...

	def member_age(self, **kwargs):
		print('kwargs: ', kwargs)
		if kwargs.get('ref_date'):
			# assume 'ref_date' is a date
			ref_date = kwargs.get('ref_date')
		else:
			ref_date = datetime.now().date()
			
		# check that 'dob' is not null
		if self.dob:
			age = ref_date.year - self.dob.year - int((ref_date.month, ref_date.day) < (self.dob.month, self.dob.day))
			# if over 18
			if age >= 18:
				age = '18+'
		else:
			age = 'unknown'
			
		return age

I can run this in the shell, and it appears to give the returns required. Now I am trying to use it in a view, with a form, where ref_date can be variable. How can I take the form data and pass that to the model method in order to get the expected output?

The form is not filtering or entering data, its sole purpose is to influence the member_age() output.

Thanks

I think we need more details here about the view and form where you’re trying to do this. Please post the view here.

Can you explain - perhaps by showing the code - what you mean by this? If you’re not entering data, what does the form do for you?

The form is just an entry method for ref_date hopefully triggering the appropriate output. ie, the method works like this:

If the Member.dob is 2020-04-01:

the_date = datetime.strptime('2024-12-31', '%Y-%m-%d')
kwargs = {'ref_date': the_date)

m = Members.objects.all()[0] # the member

m.member_age()
>>> 3 
# because there's no date input, so defaults to now()

m.member_age(**kwargs)
>>> 4

Mostly, you’ll want a list of members with their age now, but sometimes you’ll want the age at some date in the future – actually, you’ll probably want the default to be the end of the year, but either way you will want to vary that.

The view is just a ListView of all members (paginated), which displays their names and ages.

Please post the actual view that you are trying to do this with.

I can’t see any value to having a form involved here from what you’re posting so far.

I don’t think the view really helps illuminate the issue, but:

class ReportsAgeView(AuthClass, SortableListMixin, SearchableListMixin, SearchFormMixin, ListView): #

	http_method_names = ['get', 'post']

	#model = Member
	template_name = 'reports/age-list.html'

	queryset = Member.objects.filter(groups__is_participation=True, is_inactive=False).order_by('dob')
	
	form_class = ControlSearchAgeForm
	
	paginate_by = settings.DEFAULT_PAGINATION

The form has a field date, the purpose of which is to change the output of .member_age()

So, in a template:

{% for m in object_list %}
<p>{{ m.display_name }}, {{ m.member_age }}</p>
{% endfor %}

The form date is to influence the output of m.member_age, so that different values for date output different values for m.member_age.

Except you don’t do that in the template - that’s where I think the misunderstanding lies. You need to perform this type of data manipulation in the view. At some point in the view you need to iterate over the elements to be displayed, calling the member_age method to set an attribute on the element.
(Or, you could turn this into a query expression and annotate the data in your query.)

Side note: The fact that you’re getting this value from a form really doesn’t make a difference here.

Well, I think I am not trying to do anything in the template, other than output the name and age. The logic to output the name and age are in the model. So, In this case, because I want a consistent way to output names, that I don’t have to recode in different views or on templates, the display_name is a method in the Member model:

	def display_name(self):
		return_name = str(self.first_name) + ' '
		if self.known_as:
			return_name += '(' + str(self.known_as) + ') '
		return_name += str(self.last_name)
		
		return return_name

I don’t think there is anything controversial about that.

Similarly, the member_age is also a method in the Member model, and by default it gives the age “today” – that method is above, so I don’t need to post that again.

Rather than trying to make the computation of the age in the template (which I think is not so difficult with a filter) I am making the computation in the model. The “trick” is trying to get the date parameter to the model instance, which is what I am trying to do.

If you are saying that cannot be done, and that I have to somehow iterate over the object list to insert values into a Queryset (or other iterable), then I think it is easier to either make a filter, or as you indicate annotate the query.

You are.

You’re trying to call a model method with a parameter.

Quoting directly from the docs for Variables

If a variable resolves to a callable, the template system will call it with no arguments and use its result instead of the callable.

Yes, that’s an option as well.

After some faffing around, I realised that the easiest way to do this was to use the Member.member_age() method, leveraged through a simple tag:

@register.simple_tag
def show_age(m, **kwargs):
	return m.member_age(**kwargs)

then call it on the template:

{% for m in object_list %}
    {% show_age m ref_age=ref_age %}
{% endfor %}

Where the ref_age variable has been validated/cleaned and injected into the context.