diff --git a/docs/api/wuttafarm.cli.base.rst b/docs/api/wuttafarm.cli.base.rst new file mode 100644 index 0000000..19afd5c --- /dev/null +++ b/docs/api/wuttafarm.cli.base.rst @@ -0,0 +1,6 @@ + +``wuttafarm.cli.base`` +====================== + +.. automodule:: wuttafarm.cli.base + :members: diff --git a/docs/api/wuttafarm.cli.import_farmos.rst b/docs/api/wuttafarm.cli.import_farmos.rst new file mode 100644 index 0000000..12a6d03 --- /dev/null +++ b/docs/api/wuttafarm.cli.import_farmos.rst @@ -0,0 +1,6 @@ + +``wuttafarm.cli.import_farmos`` +=============================== + +.. automodule:: wuttafarm.cli.import_farmos + :members: diff --git a/docs/api/wuttafarm.cli.install.rst b/docs/api/wuttafarm.cli.install.rst new file mode 100644 index 0000000..e825989 --- /dev/null +++ b/docs/api/wuttafarm.cli.install.rst @@ -0,0 +1,6 @@ + +``wuttafarm.cli.install`` +========================= + +.. automodule:: wuttafarm.cli.install + :members: diff --git a/docs/api/wuttafarm.importing.farmos.rst b/docs/api/wuttafarm.importing.farmos.rst new file mode 100644 index 0000000..b6e00b4 --- /dev/null +++ b/docs/api/wuttafarm.importing.farmos.rst @@ -0,0 +1,6 @@ + +``wuttafarm.importing.farmos`` +============================== + +.. automodule:: wuttafarm.importing.farmos + :members: diff --git a/docs/api/wuttafarm.importing.rst b/docs/api/wuttafarm.importing.rst new file mode 100644 index 0000000..5c331b9 --- /dev/null +++ b/docs/api/wuttafarm.importing.rst @@ -0,0 +1,6 @@ + +``wuttafarm.importing`` +======================= + +.. automodule:: wuttafarm.importing + :members: diff --git a/docs/index.rst b/docs/index.rst index 4c7887b..a68b748 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,9 +8,6 @@ and extend `farmOS`_. .. _WuttaWeb: https://wuttaproject.org .. _farmOS: https://farmos.org -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - It is just an experiment so far; the ideas I hope to play with include: @@ -19,6 +16,9 @@ include: - possibly add more schema / extra features - possibly sync data back to farmOS +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + .. toctree:: :maxdepth: 2 @@ -37,11 +37,16 @@ include: api/wuttafarm.app api/wuttafarm.auth api/wuttafarm.cli + api/wuttafarm.cli.base + api/wuttafarm.cli.import_farmos + api/wuttafarm.cli.install api/wuttafarm.config api/wuttafarm.db api/wuttafarm.db.model api/wuttafarm.farmos api/wuttafarm.farmos.handler + api/wuttafarm.importing + api/wuttafarm.importing.farmos api/wuttafarm.install api/wuttafarm.web api/wuttafarm.web.app diff --git a/pyproject.toml b/pyproject.toml index fbc8df2..4c3d4d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = [ "psycopg2", "pyramid_exclog", "uvicorn[standard]", + "WuttaSync", "WuttaWeb[continuum]>=0.27.4", ] @@ -47,12 +48,18 @@ docs = ["Sphinx", "furo"] [project.entry-points."paste.app_factory"] "main" = "wuttafarm.web.app:main" +[project.entry-points."wutta.app.providers"] +wuttafarm = "wuttafarm.app:WuttaFarmAppProvider" + [project.entry-points."wutta.config.extensions"] "wuttafarm" = "wuttafarm.config:WuttaFarmConfig" [project.entry-points."wutta.web.menus"] "wuttafarm" = "wuttafarm.web.menus:WuttaFarmMenuHandler" +[project.entry-points."wuttasync.importing"] +"import.to_wuttafarm.from_farmos" = "wuttafarm.importing.farmos:FromFarmOSToWuttaFarm" + [project.urls] Homepage = "https://forgejo.wuttaproject.org/wutta/wuttafarm" diff --git a/src/wuttafarm/app.py b/src/wuttafarm/app.py index 26c6ef8..72dd675 100644 --- a/src/wuttafarm/app.py +++ b/src/wuttafarm/app.py @@ -64,3 +64,11 @@ class WuttaFarmAppHandler(base.AppHandler): """ handler = self.get_farmos_handler() return handler.get_farmos_client(*args, **kwargs) + + +class WuttaFarmAppProvider(base.AppProvider): + """ + The :term:`app provider` for WuttaFarm. + """ + + email_modules = ["wuttafarm.emails"] diff --git a/src/wuttafarm/cli/__init__.py b/src/wuttafarm/cli/__init__.py new file mode 100644 index 0000000..7f6c2bb --- /dev/null +++ b/src/wuttafarm/cli/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +WuttaFarm CLI +""" + +from .base import wuttafarm_typer + +# nb. must bring in all modules for discovery to work +from . import import_farmos +from . import install diff --git a/src/wuttafarm/cli/base.py b/src/wuttafarm/cli/base.py new file mode 100644 index 0000000..de16ead --- /dev/null +++ b/src/wuttafarm/cli/base.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +WuttaFarm CLI - base Typer instance +""" + +from wuttjamaican.cli import make_typer + + +wuttafarm_typer = make_typer( + name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS" +) diff --git a/src/wuttafarm/cli/import_farmos.py b/src/wuttafarm/cli/import_farmos.py new file mode 100644 index 0000000..4343d43 --- /dev/null +++ b/src/wuttafarm/cli/import_farmos.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +See also: :ref:`wuttafarm-import-farmos` +""" + +import typer + +from wuttasync.cli import import_command, ImportCommandHandler + +from wuttafarm.cli import wuttafarm_typer + + +@wuttafarm_typer.command() +@import_command +def import_farmos(ctx: typer.Context, **kwargs): + """ + Import data from farmOS API to WuttaFarm + """ + config = ctx.parent.wutta_config + handler = ImportCommandHandler(config, key="import.to_wuttafarm.from_farmos") + handler.run(ctx) diff --git a/src/wuttafarm/cli.py b/src/wuttafarm/cli/install.py similarity index 89% rename from src/wuttafarm/cli.py rename to src/wuttafarm/cli/install.py index 2f377a3..c82dab2 100644 --- a/src/wuttafarm/cli.py +++ b/src/wuttafarm/cli/install.py @@ -25,12 +25,7 @@ WuttaFarm CLI import typer -from wuttjamaican.cli import make_typer - - -wuttafarm_typer = make_typer( - name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS" -) +from wuttafarm.cli import wuttafarm_typer @wuttafarm_typer.command() diff --git a/src/wuttafarm/db/alembic/versions/2b6385d0fa17_add_animal_types.py b/src/wuttafarm/db/alembic/versions/2b6385d0fa17_add_animal_types.py new file mode 100644 index 0000000..7ddc814 --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/2b6385d0fa17_add_animal_types.py @@ -0,0 +1,120 @@ +"""add Animal Types + +Revision ID: 2b6385d0fa17 +Revises: +Create Date: 2026-02-08 14:55:42.236918 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "2b6385d0fa17" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = ("wuttafarm",) +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # animal_type + op.create_table( + "animal_type", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("description", sa.String(length=255), nullable=True), + sa.Column("changed", sa.DateTime(), nullable=True), + sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True), + sa.Column("drupal_internal_id", sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_animal_type")), + sa.UniqueConstraint( + "drupal_internal_id", name=op.f("uq_animal_type_drupal_internal_id") + ), + sa.UniqueConstraint("farmos_uuid", name=op.f("uq_animal_type_farmos_uuid")), + sa.UniqueConstraint("name", name=op.f("uq_animal_type_name")), + ) + op.create_table( + "animal_type_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True), + sa.Column( + "description", sa.String(length=255), autoincrement=False, nullable=True + ), + sa.Column( + "farmos_uuid", + wuttjamaican.db.util.UUID(), + autoincrement=False, + nullable=True, + ), + sa.Column( + "drupal_internal_id", sa.Integer(), autoincrement=False, nullable=True + ), + sa.Column( + "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False + ), + sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), + sa.Column("operation_type", sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint( + "uuid", "transaction_id", name=op.f("pk_animal_type_version") + ), + ) + op.create_index( + op.f("ix_animal_type_version_end_transaction_id"), + "animal_type_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_animal_type_version_operation_type"), + "animal_type_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_animal_type_version_pk_transaction_id", + "animal_type_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_animal_type_version_pk_validity", + "animal_type_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_animal_type_version_transaction_id"), + "animal_type_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # animal_type + op.drop_index( + op.f("ix_animal_type_version_transaction_id"), table_name="animal_type_version" + ) + op.drop_index( + "ix_animal_type_version_pk_validity", table_name="animal_type_version" + ) + op.drop_index( + "ix_animal_type_version_pk_transaction_id", table_name="animal_type_version" + ) + op.drop_index( + op.f("ix_animal_type_version_operation_type"), table_name="animal_type_version" + ) + op.drop_index( + op.f("ix_animal_type_version_end_transaction_id"), + table_name="animal_type_version", + ) + op.drop_table("animal_type_version") + op.drop_table("animal_type") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index b52d7c8..d0693cb 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -26,4 +26,5 @@ WuttaFarm data models # bring in all of wutta from wuttjamaican.db.model import * -# TODO: import other/custom models here... +# wuttafarm models +from .animals import AnimalType diff --git a/src/wuttafarm/db/model/animals.py b/src/wuttafarm/db/model/animals.py new file mode 100644 index 0000000..a26b966 --- /dev/null +++ b/src/wuttafarm/db/model/animals.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Model definition for Animal Types +""" + +import sqlalchemy as sa +from sqlalchemy import orm + +from wuttjamaican.db import model + + +class AnimalType(model.Base): + """ + Represents an "animal type" (taxonomy term) from farmOS + """ + + __tablename__ = "animal_type" + __versioned__ = { + "exclude": [ + "changed", + ], + } + __wutta_hint__ = { + "model_title": "Animal Type", + "model_title_plural": "Animal Types", + } + + uuid = model.uuid_column() + + name = sa.Column( + sa.String(length=100), + nullable=False, + unique=True, + doc=""" + Name of the animal type. + """, + ) + + description = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Optional description for the animal type. + """, + ) + + changed = sa.Column( + sa.DateTime(), + nullable=True, + doc=""" + When the animal type was last changed, according to farmOS. + """, + ) + + farmos_uuid = sa.Column( + model.UUID(), + nullable=True, + unique=True, + doc=""" + UUID for the animal type within farmOS. + """, + ) + + drupal_internal_id = sa.Column( + sa.Integer(), + nullable=True, + unique=True, + doc=""" + Drupal internal ID for the animal type. + """, + ) + + def __str__(self): + return self.name or "" diff --git a/src/wuttafarm/emails.py b/src/wuttafarm/emails.py new file mode 100644 index 0000000..55b1612 --- /dev/null +++ b/src/wuttafarm/emails.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Email sending config for WuttaFarm +""" + +from wuttasync.emails import ImportExportWarning + + +class import_to_wuttafarm_from_farmos_warning(ImportExportWarning): + """ + Diff warning for farmOS → WuttaFarm import. + """ diff --git a/src/wuttafarm/importing/__init__.py b/src/wuttafarm/importing/__init__.py new file mode 100644 index 0000000..6711d56 --- /dev/null +++ b/src/wuttafarm/importing/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Importing data to WuttaFarm +""" diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py new file mode 100644 index 0000000..e9e4735 --- /dev/null +++ b/src/wuttafarm/importing/farmos.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Data import for farmOS -> WuttaFarm +""" + +import datetime +from uuid import UUID + +from oauthlib.oauth2 import BackendApplicationClient +from requests_oauthlib import OAuth2Session + +from wuttasync.importing import ImportHandler, ToWuttaHandler, Importer, ToWutta + +from wuttafarm.db import model + + +class FromFarmOSHandler(ImportHandler): + """ + Base class for import handler using farmOS API as data source. + """ + + source_key = "farmos" + generic_source_title = "farmOS" + + def begin_source_transaction(self): + """ + Establish the farmOS API client. + """ + token = self.get_farmos_oauth2_token() + self.farmos_client = self.app.get_farmos_client(token=token) + + def get_farmos_oauth2_token(self): + + client_id = self.config.get( + "farmos.oauth2.importing.client_id", default="wuttafarm" + ) + client_secret = self.config.require("farmos.oauth2.importing.client_secret") + scope = self.config.get("farmos.oauth2.importing.scope", default="farm_manager") + + client = BackendApplicationClient(client_id=client_id) + oauth = OAuth2Session(client=client) + + return oauth.fetch_token( + token_url=self.app.get_farmos_url("/oauth/token"), + include_client_id=True, + client_secret=client_secret, + scope=scope, + ) + + def get_importer_kwargs(self, key, **kwargs): + kwargs = super().get_importer_kwargs(key, **kwargs) + kwargs["farmos_client"] = self.farmos_client + return kwargs + + +class ToWuttaFarmHandler(ToWuttaHandler): + """ + Base class for import handler targeting WuttaFarm + """ + + target_key = "wuttafarm" + + +class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler): + """ + Handler for farmOS → WuttaFarm import. + """ + + def define_importers(self): + """ """ + importers = super().define_importers() + importers["AnimalType"] = AnimalTypeImporter + return importers + + +class FromFarmOS(Importer): + """ + Base class for importers using farmOS API as data source. + """ + + key = "farmos_uuid" + + def get_supported_fields(self): + """ + Auto-remove the ``uuid`` field, since we use ``farmos_uuid`` + instead for the importer key. + """ + fields = list(super().get_supported_fields()) + if "uuid" in fields: + fields.remove("uuid") + return fields + + def normalize_datetime(self, dt): + """ + Convert a farmOS datetime value to naive UTC used by + WuttaFarm. + + :param dt: Date/time string value "as-is" from the farmOS API. + + :returns: Equivalent naive UTC ``datetime`` + """ + dt = datetime.datetime.fromisoformat(dt) + return self.app.make_utc(dt) + + +class AnimalTypeImporter(FromFarmOS, ToWutta): + """ + farmOS API → WuttaFarm importer for Animal Types + """ + + model_class = model.AnimalType + + supported_fields = [ + "farmos_uuid", + "drupal_internal_id", + "name", + "description", + "changed", + ] + + def get_source_objects(self): + """ """ + animal_types = self.farmos_client.resource.get("taxonomy_term", "animal_type") + return animal_types["data"] + + def normalize_source_object(self, animal_type): + """ """ + return { + "farmos_uuid": UUID(animal_type["id"]), + "drupal_internal_id": animal_type["attributes"]["drupal_internal__tid"], + "name": animal_type["attributes"]["name"], + "description": animal_type["attributes"]["description"], + "changed": self.normalize_datetime(animal_type["attributes"]["changed"]), + } diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py index ab6f440..7571b3c 100644 --- a/src/wuttafarm/web/menus.py +++ b/src/wuttafarm/web/menus.py @@ -33,10 +33,24 @@ class WuttaFarmMenuHandler(base.MenuHandler): def make_menus(self, request, **kwargs): return [ + self.make_asset_menu(request), self.make_farmos_menu(request), self.make_admin_menu(request, include_people=True), ] + def make_asset_menu(self, request): + return { + "title": "Assets", + "type": "menu", + "items": [ + { + "title": "Animal Types", + "route": "animal_types", + "perm": "animal_types.list", + }, + ], + } + def make_farmos_menu(self, request): config = request.wutta_config app = config.get_app() diff --git a/src/wuttafarm/web/views/__init__.py b/src/wuttafarm/web/views/__init__.py index 63ce536..86dcd81 100644 --- a/src/wuttafarm/web/views/__init__.py +++ b/src/wuttafarm/web/views/__init__.py @@ -25,6 +25,8 @@ WuttaFarm Views from wuttaweb.views import essential +from .master import WuttaFarmMasterView + def includeme(config): @@ -37,5 +39,8 @@ def includeme(config): } ) + # native table views + config.include("wuttafarm.web.views.animal_types") + # views for farmOS config.include("wuttafarm.web.views.farmos") diff --git a/src/wuttafarm/web/views/animal_types.py b/src/wuttafarm/web/views/animal_types.py new file mode 100644 index 0000000..ecd136c --- /dev/null +++ b/src/wuttafarm/web/views/animal_types.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Master view for Animal Types +""" + +from wuttafarm.db.model.animals import AnimalType +from wuttafarm.web.views import WuttaFarmMasterView + + +class AnimalTypeView(WuttaFarmMasterView): + """ + Master view for Animal Types + """ + + model_class = AnimalType + route_prefix = "animal_types" + url_prefix = "/animal-types" + + farmos_refurl_path = "/admin/structure/taxonomy/manage/animal_type/overview" + + grid_columns = [ + "name", + "description", + "changed", + ] + + sort_defaults = "name" + + filter_defaults = { + "name": {"active": True, "verb": "contains"}, + } + + form_fields = [ + "name", + "description", + "changed", + "farmos_uuid", + "drupal_internal_id", + ] + + def configure_grid(self, grid): + g = grid + super().configure_grid(g) + + # name + g.set_link("name") + + def get_farmos_url(self, animal_type): + return self.app.get_farmos_url( + f"/taxonomy/term/{animal_type.drupal_internal_id}" + ) + + def get_xref_buttons(self, animal_type): + buttons = super().get_xref_buttons(animal_type) + + if animal_type.farmos_uuid: + buttons.append( + self.make_button( + "View farmOS record", + primary=True, + url=self.request.route_url( + "farmos_animal_types.view", uuid=animal_type.farmos_uuid + ), + icon_left="eye", + ) + ) + + return buttons + + +def defaults(config, **kwargs): + base = globals() + + AnimalTypeView = kwargs.get("AnimalTypeView", base["AnimalTypeView"]) + AnimalTypeView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/src/wuttafarm/web/views/farmos/animal_types.py b/src/wuttafarm/web/views/farmos/animal_types.py index 0e8e4df..ae0b0d4 100644 --- a/src/wuttafarm/web/views/farmos/animal_types.py +++ b/src/wuttafarm/web/views/farmos/animal_types.py @@ -113,7 +113,10 @@ class AnimalTypeView(FarmOSMasterView): f.set_node("changed", WuttaDateTime()) def get_xref_buttons(self, animal_type): - return [ + model = self.app.model + session = self.Session() + + buttons = [ self.make_button( "View in farmOS", primary=True, @@ -122,9 +125,27 @@ class AnimalTypeView(FarmOSMasterView): ), target="_blank", icon_left="external-link-alt", - ), + ) ] + if wf_animal_type := ( + session.query(model.AnimalType) + .filter(model.AnimalType.farmos_uuid == animal_type["uuid"]) + .first() + ): + buttons.append( + self.make_button( + f"View {self.app.get_title()} record", + primary=True, + url=self.request.route_url( + "animal_types.view", uuid=wf_animal_type.uuid + ), + icon_left="eye", + ) + ) + + return buttons + def defaults(config, **kwargs): base = globals() diff --git a/src/wuttafarm/web/views/master.py b/src/wuttafarm/web/views/master.py new file mode 100644 index 0000000..69a7f89 --- /dev/null +++ b/src/wuttafarm/web/views/master.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Base class for WuttaFarm master views +""" + +from wuttaweb.views import MasterView + + +class WuttaFarmMasterView(MasterView): + """ + Base class for WuttaFarm master views + """ + + farmos_refurl_path = None + + labels = { + "farmos_uuid": "farmOS UUID", + "drupal_internal_id": "Drupal Internal ID", + } + + def get_farmos_url(self, obj): + return None + + def get_template_context(self, context): + + if self.listing and self.farmos_refurl_path: + context["farmos_refurl"] = self.app.get_farmos_url(self.farmos_refurl_path) + + return context + + def get_xref_buttons(self, obj): + url = self.get_farmos_url(obj) + if url: + return [ + self.make_button( + "View in farmOS", + primary=True, + url=url, + target="_blank", + icon_left="external-link-alt", + ) + ] + return []