Skip to content

Add support for 'tags_exclude' to let user specify tags to exclude photos#545

Open
greensum wants to merge 1 commit intohelgeerbe:mainfrom
greensum:feature/tags_exclude
Open

Add support for 'tags_exclude' to let user specify tags to exclude photos#545
greensum wants to merge 1 commit intohelgeerbe:mainfrom
greensum:feature/tags_exclude

Conversation

@greensum
Copy link

@greensum greensum commented Sep 13, 2025

As a follow up to my discussion on my need to exclude some photos, I made a simple enhancement to add the support for specifying tags to exclude. This is needed even though the existing 'tags_filter' can take expressions like 'NOT (private OR hidden)' as that would only select photos that have at least some tags, as long as they are not 'private' nor 'hidden'. With 'tags_exclude' it will show photos that don't have any tags, and of course it will exclude photo that have the matching tags. Like the 'tags_filter', 'tags_exclude' can be set in the configuration.yaml file, through MQTT, and through HTTP.

Summary by Sourcery

Add a new tags_exclude filter to allow users to exclude photos by tag across all interfaces

New Features:

  • Introduce a tags_exclude property in the model with SQL clause generation to exclude matching and untagged photos

Enhancements:

  • Expose tags_exclude setter in the controller to refresh views or stop video playback

photos from being displayed.  Like the 'tags_filter', it can be set
in the configuration.yaml file, through MQTT, and through HTTP.
This is needed even though the 'tags_filter' can take expressions
like 'NOT (private OR hidden)' as that would only select photos
that have at lease some tags, as long as they are not 'private'
nor 'hidden'.  With 'tags_exclude' it will show photos that don't
have any tags, and of course it will exclude photo that have the
matching tags.
@sourcery-ai
Copy link

sourcery-ai bot commented Sep 13, 2025

Reviewer's Guide

Introduces a new tags_exclude filter analogous to tags_filter by adding its configuration, integrating it into the data model, controller, MQTT and HTTP interfaces, UI elements, and example configuration to let users exclude photos by tag (including untagged items).

Sequence diagram for setting tags_exclude via MQTT

sequenceDiagram
participant MQTT
participant "interface_mqtt"
participant Controller
participant Model
MQTT->>interface_mqtt: Send /tags_exclude message
interface_mqtt->>Controller: Set tags_exclude
Controller->>Model: Set tags_exclude
Model->>Model: Update filter and reload files
Loading

Sequence diagram for setting tags_exclude via HTTP

sequenceDiagram
actor User
participant HTTP_Interface
participant Controller
participant Model
User->>HTTP_Interface: Send request to set tags_exclude
HTTP_Interface->>Controller: Set tags_exclude
Controller->>Model: Set tags_exclude
Model->>Model: Update filter and reload files
Loading

Class diagram for updated Model and Controller with tags_exclude support

classDiagram
class Model {
  - location_filter: str
  - tags_filter: str
  - tags_exclude: str
  + get_viewer_config()
  + tags_filter
  + tags_exclude
}
class Controller {
  - __model: Model
  + tags_filter
  + tags_exclude
}
Model <|-- Controller
Loading

File-Level Changes

Change Details Files
Introduce tags_exclude in data model
  • Add default tags_exclude config entry
  • Implement tags_exclude property getter/setter
  • Extend where_clause logic to filter out matching tags and include untagged items
  • Trigger reload on tags_exclude updates
src/picframe/model.py
Extend controller to proxy tags_exclude
  • Add getter and setter for tags_exclude in controller
  • Stop video or reset timer on tags_exclude changes
src/picframe/controller.py
Add MQTT support for tags_exclude
  • Register tags_exclude config topic and sensor
  • Handle incoming tags_exclude messages
  • Publish tags_exclude state in MQTT payload
src/picframe/interface_mqtt.py
Update HTTP interface and UI for tags_exclude
  • Accept tags_exclude parameter in HTTP GET parsing
  • Add tags_exclude control element in HTML index
src/picframe/interface_http.py
src/picframe/html/index.html
Document tags_exclude in example config
  • Add tags_exclude entry under model section in configuration example
src/picframe/config/configuration_example.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/picframe/model.py:325` </location>
<code_context>
+    @tags_exclude.setter
+    def tags_exclude(self, val):
+        self.__config['model']['tags_exclude'] = val
+        if len(val) > 0:
+            ftr = self.__build_filter(val, "tags")
+            if ftr:
</code_context>

<issue_to_address>
Using 'len(val) > 0' assumes val is always a string.

If 'tags_exclude' can be non-string (e.g., None), this may cause an error. Use 'if val:' or ensure 'val' is always a string.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        if len(val) > 0:
            ftr = self.__build_filter(val, "tags")
            if ftr:
                ftr = "(tags IS NULL OR NOT {})".format(ftr)
            self.set_where_clause("tags_exclude", ftr)
        else:
            self.set_where_clause("tags_exclude")  # remove from where_clause
        self.__reload_files = True
=======
        if val:
            ftr = self.__build_filter(val, "tags")
            if ftr:
                ftr = "(tags IS NULL OR NOT {})".format(ftr)
            self.set_where_clause("tags_exclude", ftr)
        else:
            self.set_where_clause("tags_exclude")  # remove from where_clause
        self.__reload_files = True
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `src/picframe/interface_mqtt.py:719` </location>
<code_context>
             self.__logger.info("Recieved tags filter: %s", msg)
             self.__controller.tags_filter = msg
+        # tags exclude filter
+        elif message.topic == self.__device_id + "/tags_exclude":
+            self.__logger.info("Recieved tags exclude filter: %s", msg)
+            self.__controller.tags_exclude = msg

</code_context>

<issue_to_address>
Typo in log message: 'Recieved' should be 'Received'.

Fixing the typo ensures clear and professional log output.

Suggested implementation:

```python
            self.__logger.info("Received tags filter: %s", msg)

```

```python
            self.__logger.info("Received tags exclude filter: %s", msg)

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +325 to +332
if len(val) > 0:
ftr = self.__build_filter(val, "tags")
if ftr:
ftr = "(tags IS NULL OR NOT {})".format(ftr)
self.set_where_clause("tags_exclude", ftr)
else:
self.set_where_clause("tags_exclude") # remove from where_clause
self.__reload_files = True
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Using 'len(val) > 0' assumes val is always a string.

If 'tags_exclude' can be non-string (e.g., None), this may cause an error. Use 'if val:' or ensure 'val' is always a string.

Suggested change
if len(val) > 0:
ftr = self.__build_filter(val, "tags")
if ftr:
ftr = "(tags IS NULL OR NOT {})".format(ftr)
self.set_where_clause("tags_exclude", ftr)
else:
self.set_where_clause("tags_exclude") # remove from where_clause
self.__reload_files = True
if val:
ftr = self.__build_filter(val, "tags")
if ftr:
ftr = "(tags IS NULL OR NOT {})".format(ftr)
self.set_where_clause("tags_exclude", ftr)
else:
self.set_where_clause("tags_exclude") # remove from where_clause
self.__reload_files = True

Comment on lines +719 to +720
elif message.topic == self.__device_id + "/tags_exclude":
self.__logger.info("Recieved tags exclude filter: %s", msg)
Copy link

Choose a reason for hiding this comment

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

nitpick (typo): Typo in log message: 'Recieved' should be 'Received'.

Fixing the typo ensures clear and professional log output.

Suggested implementation:

            self.__logger.info("Received tags filter: %s", msg)
            self.__logger.info("Received tags exclude filter: %s", msg)

if len(val) > 0:
ftr = self.__build_filter(val, "tags")
if ftr:
ftr = "(tags IS NULL OR NOT {})".format(ftr)
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Replace call to format with f-string (use-fstring-for-formatting)

Suggested change
ftr = "(tags IS NULL OR NOT {})".format(ftr)
ftr = f"(tags IS NULL OR NOT {ftr})"

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.

1 participant