Repository: beetbox/beets

Issue:
%ifdef does not work on flexattr fields with int or float
### Problem
If I try `beet ls -a -f '$albumartist - (%if{$original_year,$original_year,$year}) - $album%ifdef{albumdisambig, (%title{$albumdisambig}),}%ifdef{albumrating, ($albumrating),}' year:2021` then beets will return the album rating for the albums that has the field defined. For albums without the rating, `$albumrating` is returned.

Example:
```
Artist1 - (2021) - Albumname1 (2.0)
Artist2 - (2021) - Albumname2 ($albumrating)
```

Further information on the forum:
https://discourse.beets.io/t/album-rating-as-a-flexible-attribute-in-5-increments/1687/3

Comment from adrian:
_reading over the implementation of %ifdef, I can see now how it would be confused about types with a default value. That is, int or float fields will produce a '0' string for unset values through the formatter, which gets interpreted as non-missing. We clearly need better logic for this._

### Setup

* OS: Arch Linux
* beets version 1.5.0
* Python version 3.9.1
* plugins: edit, extrafiles, fetchart, importadded, importfeeds, info, inline, lastimport, mbcollection, mbsync, mpdstats, mpdupdate, originquery, scrub, smartplaylist, types, zero



Discussion:
Thanks! The right fix here is to change the `tmpl_ifdef` method to look directly for the presence of a field rather than trying to go through the formatter.
I'm also facing this issue and glad to know it can be fixed. I thought it was my own user error though.
Hello, I would like to work on this bug.

Resolving patch:
diff --git a/beets/library.py b/beets/library.py
--- a/beets/library.py
+++ b/beets/library.py
@@ -1753,7 +1753,7 @@ def tmpl_ifdef(self, field, trueval=u'', falseval=u''):
         :param falseval: The string if the condition is false
         :return: The string, based on condition
         """
-        if self.item.formatted().get(field):
+        if field in self.item:
             return trueval if trueval else self.item.formatted().get(field)
         else:
             return falseval


Test patch:
diff --git a/test/test_types_plugin.py b/test/test_types_plugin.py
--- a/test/test_types_plugin.py
+++ b/test/test_types_plugin.py
@@ -145,6 +145,39 @@ def test_unknown_type_error(self):
         with self.assertRaises(ConfigValueError):
             self.run_command(u'ls')
 
+    def test_template_if_def(self):
+        # Tests for a subtle bug when using %ifdef in templates along with
+        # types that have truthy default values (e.g. '0', '0.0', 'False')
+        # https://github.com/beetbox/beets/issues/3852
+        self.config['types'] = {'playcount': u'int', 'rating': u'float',
+                                'starred': u'bool'}
+
+        with_fields = self.add_item(artist=u'prince')
+        self.modify(u'playcount=10', u'artist=prince')
+        self.modify(u'rating=5.0', u'artist=prince')
+        self.modify(u'starred=yes', u'artist=prince')
+        with_fields.load()
+
+        without_fields = self.add_item(artist=u'britney')
+
+        int_template = u'%ifdef{playcount,Play count: $playcount,Not played}'
+        self.assertEqual(with_fields.evaluate_template(int_template),
+                         u'Play count: 10')
+        self.assertEqual(without_fields.evaluate_template(int_template),
+                         u'Not played')
+
+        float_template = u'%ifdef{rating,Rating: $rating,Not rated}'
+        self.assertEqual(with_fields.evaluate_template(float_template),
+                         u'Rating: 5.0')
+        self.assertEqual(without_fields.evaluate_template(float_template),
+                         u'Not rated')
+
+        bool_template = u'%ifdef{starred,Starred: $starred,Not starred}'
+        self.assertIn(with_fields.evaluate_template(bool_template).lower(),
+                      (u'starred: true', u'starred: yes', u'starred: y'))
+        self.assertEqual(without_fields.evaluate_template(bool_template),
+                         u'Not starred')
+
     def modify(self, *args):
         return self.run_with_output(u'modify', u'--yes', u'--nowrite',
                                     u'--nomove', *args)