Skip to content

Add dietary property fields (is_vegan, is_vegetarian) to Ingredient#2218

Merged
rolandgeider merged 11 commits into
wger-project:masterfrom
samtuckerdavis:feature/ingredient-dietary-properties
Mar 1, 2026
Merged

Add dietary property fields (is_vegan, is_vegetarian) to Ingredient#2218
rolandgeider merged 11 commits into
wger-project:masterfrom
samtuckerdavis:feature/ingredient-dietary-properties

Conversation

@samtuckerdavis

Copy link
Copy Markdown
Contributor

Summary

Adds is_vegan and is_vegetarian nullable boolean fields to the Ingredient model, populated from Open Food Facts ingredients_analysis_tags during import.

  • Model: Two new BooleanField(null=True) fields on Ingredientis_vegan and is_vegetarian
  • OFF extraction: Parses en:vegan, en:non-vegan, en:vegetarian, en:non-vegetarian tags; unknown/maybe states map to NULL
  • API: Fields exposed in both IngredientSerializer and IngredientInfoSerializer, filterable via ?is_vegan=true / ?is_vegetarian=true
  • Sync: extract_info_from_wger_api passes through these fields during instance-to-instance sync
  • Migration: 0028_ingredient_dietary_properties — backward-compatible, all existing rows default to NULL
  • Tests: 5 new test cases covering vegan, non-vegan, non-vegetarian, unknown status, and missing tags scenarios

Context

Open Food Facts already provides ingredient analysis data that classifies products as vegan/vegetarian/non-vegan/non-vegetarian. This PR captures that data during import so it can be used for filtering. This addresses the long-standing request in #394 for dietary categorization of ingredients.

The three-state approach (True / False / NULL) correctly handles the reality that many products have unknown dietary status — NULL means "not determined" rather than forcing a potentially incorrect classification.

Test plan

  • Existing test_off.py tests pass with updated fixtures
  • New dietary property tests cover all OFF tag variations
  • test_ingredient.py fetch-from-OFF test verifies fields propagate to saved model
  • Migration applies cleanly on existing databases (all rows get NULL defaults)
  • API filtering works: GET /api/v2/ingredient/?is_vegan=true

Extract dietary classification from Open Food Facts
ingredients_analysis_tags during import and expose via API.

- Add nullable boolean fields is_vegan and is_vegetarian to Ingredient
- Parse en:vegan, en:non-vegan, en:vegetarian, en:non-vegetarian tags
  from OFF product data (unknown/maybe states map to NULL)
- Include fields in API serializers (ingredient and ingredientinfo)
- Add filterset support for exact match filtering (?is_vegan=true)
- Handle fields in wger API sync (extract_info_from_wger_api)
- Add migration 0028_ingredient_dietary_properties
- Add tests for all OFF dietary tag parsing scenarios

Refs #394
is_vegetarian = None
analysis_tags = product_data.get('ingredients_analysis_tags', [])
for tag in analysis_tags:
if tag == 'en:vegan':

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the tags have these names for all ingredients?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tags use Open Food Facts' standardized ingredients_analysis_tags field which applies consistent tag names across all ingredients that have been analyzed. However, not all ingredients have been analyzed — for those, the field is absent or null, which is why the model fields are nullable. The tags follow OFF's fixed vocabulary: en:vegan, en:non-vegan, en:maybe-vegan, en:vegan-status-unknown, en:vegetarian, en:non-vegetarian, en:maybe-vegetarian, en:vegetarian-status-unknown. So: yes, the tags are consistent for ingredients that have them, but coverage is not 100%.

@samtuckerdavis

Copy link
Copy Markdown
Contributor Author

Hey @rolandgeider — yes, these are the standardized tag values from Open Food Facts' ingredients_analysis_tags field. OFF uses a fixed set of tags for dietary classification:

  • en:vegan / en:non-vegan / en:maybe-vegan / en:vegan-status-unknown
  • en:vegetarian / en:non-vegetarian / en:maybe-vegetarian / en:vegetarian-status-unknown

The en: prefix is the language namespace OFF uses across all their tag taxonomies — it's consistent regardless of which language the product was entered in. These tags are computed by OFF based on their ingredient analysis, so the naming is stable across the entire database.

To your review comment specifically: these aren't free-form tags that vary per ingredient. They're from a controlled vocabulary that OFF applies uniformly. The PR maps maybe-* and *-status-unknown both to NULL since neither gives us a confident True/False — seemed like the safest approach rather than guessing.

You can verify the taxonomy here: https://world.openfoodfacts.org/ingredients-analysis

Let me know if you'd want the extraction logic handled differently.

@rolandgeider

Copy link
Copy Markdown
Member

thanks, good to know! There are a couple of things that would be nice to extract as well and last time I was looking at their dataset found their data model somewhat confusing.

BTW, do you want to give the UI a shot as well or should I take a look at that?

@samtuckerdavis

Copy link
Copy Markdown
Contributor Author

@rolandgeider Yeah I'd be happy to take a shot at the UI — makes sense to wire it up while the model changes are fresh. I'll add filter controls to the ingredient list and a display in the detail view. If there are specific UI patterns or component conventions you prefer in the wger frontend, just point me at an example and I'll follow that.

And yeah, OFF's tag system is... characterful. Happy to help extract more fields if you can point me at which ones would be most useful.

- Ingredient detail view: show a "Dietary information" table with
  green/red badges for is_vegan and is_vegetarian when the data is
  present (nullable fields are hidden when null).
- Ingredient list view: display vegan/vegetarian badges next to each
  ingredient name in the list, and add sidebar filter checkboxes so
  users can narrow the list to vegan-only or vegetarian-only items.
- IngredientListView.get_queryset: apply is_vegan=True / is_vegetarian=True
  DB filters when the corresponding GET params are present; also bypass
  the page-level cache for filtered requests so results stay accurate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rolandgeider

Copy link
Copy Markdown
Member

OFF's tag system is... characterful

😆

We were looking if they had something like a "serving size", also extracting the categories would be nice (of course both of these would need other backend changes)

@samtuckerdavis

Copy link
Copy Markdown
Contributor Author

Happy to move forward with the UI once we nail down conventions. A couple of questions to make sure I get it right:

  1. Is there an existing example I should follow? e.g. how the language or license field is rendered in the ingredient detail view — would the vegan/vegetarian fields follow the same pattern (badge, checkbox, icon)?
  2. Should this backend PR merge first, or would you prefer I submit the UI work in the same PR?

Also re: serving size and categories — those sound like separate PRs given the additional backend changes they'd need. Happy to tackle them as follow-ups once this one is in.

@rolandgeider

Copy link
Copy Markdown
Member

no, at the moment there isn't much of an example. I'd say using icons for this would be a good solution, perhaps something similar to what OFF is doing?

Actually when I talked about the UI I was thinking about the autocompleter used e.g. in the meal forms, since being able to filter there by these flags (and potentially others) is what would make this feature really useful. I'll see what would need to be done here

Bildschirmfoto 2026-02-26 um 15 44 05

And the serving sizes and the categories would be of course a different PR

@samtuckerdavis

Copy link
Copy Markdown
Contributor Author

Went ahead and investigated the autocompleter infrastructure — the search flow uses /api/v2/ingredientinfo/ (not the deprecated /api/v2/ingredient/search/ endpoint), and the backend filterset in this PR already handles ?is_vegan=true / ?is_vegetarian=true correctly.

Opened a companion PR against the React app with the frontend changes: wger-project/react#1197

It adds "Vegan only" and "Vegetarian only" toggle switches to IngredientAutocompleter, wired to the API query params. The toggles degrade gracefully if the backend fields aren't present yet — they'll just return unfiltered results until this PR lands.

The search can now be done more cleanly via the regular "ingredientinfo" endpoint
so this is not needed anymore
This is mostly for completeness, these forms are rarely used since most ingredients
are just imported from OFF
This is cleaner and removes boilerplate
@samtuckerdavis

Copy link
Copy Markdown
Contributor Author

Branch updated to current with upstream master (was 17 commits behind). Merge state is now clean. Re CI: shows action_required — GitHub first-time contributor fork restriction, needs a maintainer to approve the workflow run.

@rolandgeider

Copy link
Copy Markdown
Member

finally, everything is green!

I'll merge this and the react pr later today and run a full re-import of OFF the next week so that populate the fields in the db

@rolandgeider rolandgeider merged commit 108e906 into wger-project:master Mar 1, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add plant-based / dietary preference filters to nutrition module

2 participants