kopia lustrzana https://github.com/collective/icalendar
				
				
				
			Add categories property
							rodzic
							
								
									33f554fb2f
								
							
						
					
					
						commit
						cca55fc8c2
					
				| 
						 | 
					@ -17,6 +17,7 @@ New features:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Add attributes to the calendar for properties ``NAME``, ``DESCRIPTION``, and ``COLOR``. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 | 
					- Add attributes to the calendar for properties ``NAME``, ``DESCRIPTION``, and ``COLOR``. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 | 
				
			||||||
- Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 <https://github.com/collective/icalendar/issues/802>`_.
 | 
					- Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 <https://github.com/collective/icalendar/issues/802>`_.
 | 
				
			||||||
 | 
					- Add ``categories`` attribute to ``Calendar``, ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655 <https://github.com/collective/icalendar/issues/655>`_.
 | 
				
			||||||
- Add compatibility to :rfc:`6868`. See `Issue 652 <https://github.com/collective/icalendar/issues/652>`_.
 | 
					- Add compatibility to :rfc:`6868`. See `Issue 652 <https://github.com/collective/icalendar/issues/652>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bug fixes:
 | 
					Bug fixes:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,11 @@
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import date, datetime
 | 
					from datetime import date, datetime
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
from typing import TYPE_CHECKING, Optional
 | 
					from typing import TYPE_CHECKING, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from icalendar.error import InvalidCalendar
 | 
					from icalendar.error import InvalidCalendar
 | 
				
			||||||
from icalendar.prop import vDDDTypes, vText
 | 
					from icalendar.prop import vCategory, vDDDTypes, vText
 | 
				
			||||||
from icalendar.timezone import tzp
 | 
					from icalendar.timezone import tzp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
| 
						 | 
					@ -179,9 +180,79 @@ Examples:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_categories(component: Component) -> list[str]:
 | 
				
			||||||
 | 
					    """Get all the categories."""
 | 
				
			||||||
 | 
					    categories : Optional[vCategory|list[vCategory]] = component.get("CATEGORIES")
 | 
				
			||||||
 | 
					    if isinstance(categories, list):
 | 
				
			||||||
 | 
					        _set_categories(component, list(itertools.chain.from_iterable(cat.cats for cat in categories)))
 | 
				
			||||||
 | 
					        return _get_categories(component)
 | 
				
			||||||
 | 
					    if categories is None:
 | 
				
			||||||
 | 
					        categories = vCategory([])
 | 
				
			||||||
 | 
					        component.add("CATEGORIES", categories)
 | 
				
			||||||
 | 
					    return categories.cats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _set_categories(component: Component, cats: list[str]) -> None:
 | 
				
			||||||
 | 
					    """Set the categories."""
 | 
				
			||||||
 | 
					    component["CATEGORIES"] = categories = vCategory(cats)
 | 
				
			||||||
 | 
					    cats.clear()
 | 
				
			||||||
 | 
					    cats.extend(categories.cats)
 | 
				
			||||||
 | 
					    categories.cats = cats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _del_categories(component: Component) -> None:
 | 
				
			||||||
 | 
					    """Delete the categories."""
 | 
				
			||||||
 | 
					    component.pop("CATEGORIES", None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					categories_property = property(
 | 
				
			||||||
 | 
					    _get_categories,
 | 
				
			||||||
 | 
					    _set_categories,
 | 
				
			||||||
 | 
					    _del_categories,
 | 
				
			||||||
 | 
					    """This property defines the categories for a component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Property Parameters:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IANA, non-standard, and language property parameters can be specified on this
 | 
				
			||||||
 | 
					    property.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Conformance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar
 | 
				
			||||||
 | 
					    components.
 | 
				
			||||||
 | 
					    Since :rfc:`7986` it can also be defined on a "VCALENDAR" component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This property is used to specify categories or subtypes
 | 
				
			||||||
 | 
					    of the calendar component.  The categories are useful in searching
 | 
				
			||||||
 | 
					    for a calendar component of a particular type and category.
 | 
				
			||||||
 | 
					    Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components,
 | 
				
			||||||
 | 
					    more than one category can be specified as a COMMA-separated list
 | 
				
			||||||
 | 
					    of categories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> from icalendar import Event
 | 
				
			||||||
 | 
					    >>> event = Event()
 | 
				
			||||||
 | 
					    >>> event.categories = ["Work", "Meeting"]
 | 
				
			||||||
 | 
					    >>> print(event.to_ical())
 | 
				
			||||||
 | 
					    BEGIN:VEVENT
 | 
				
			||||||
 | 
					    CATEGORIES:Work,Meeting
 | 
				
			||||||
 | 
					    END:VEVENT
 | 
				
			||||||
 | 
					    >>> event.categories.append("Lecture")
 | 
				
			||||||
 | 
					    >>> event.categories == ["Work", "Meeting", "Lecture"]
 | 
				
			||||||
 | 
					    True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   At present, we do not take the LANGUAGE parameter into account.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = [
 | 
					__all__ = [
 | 
				
			||||||
    "single_utc_property",
 | 
					    "single_utc_property",
 | 
				
			||||||
    "multi_language_text_property",
 | 
					    "multi_language_text_property",
 | 
				
			||||||
    "single_int_property",
 | 
					    "single_int_property",
 | 
				
			||||||
    "sequence_property"
 | 
					    "sequence_property",
 | 
				
			||||||
 | 
					    "categories_property",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import dateutil.rrule
 | 
				
			||||||
import dateutil.tz
 | 
					import dateutil.tz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from icalendar.attr import (
 | 
					from icalendar.attr import (
 | 
				
			||||||
 | 
					    categories_property,
 | 
				
			||||||
    multi_language_text_property,
 | 
					    multi_language_text_property,
 | 
				
			||||||
    sequence_property,
 | 
					    sequence_property,
 | 
				
			||||||
    single_int_property,
 | 
					    single_int_property,
 | 
				
			||||||
| 
						 | 
					@ -894,6 +895,7 @@ class Event(Component):
 | 
				
			||||||
    X_MOZ_SNOOZE_TIME = _X_MOZ_SNOOZE_TIME
 | 
					    X_MOZ_SNOOZE_TIME = _X_MOZ_SNOOZE_TIME
 | 
				
			||||||
    X_MOZ_LASTACK = _X_MOZ_LASTACK
 | 
					    X_MOZ_LASTACK = _X_MOZ_LASTACK
 | 
				
			||||||
    sequence = sequence_property
 | 
					    sequence = sequence_property
 | 
				
			||||||
 | 
					    categories = categories_property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Todo(Component):
 | 
					class Todo(Component):
 | 
				
			||||||
| 
						 | 
					@ -1083,6 +1085,7 @@ class Todo(Component):
 | 
				
			||||||
        return Alarms(self)
 | 
					        return Alarms(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sequence = sequence_property
 | 
					    sequence = sequence_property
 | 
				
			||||||
 | 
					    categories = categories_property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Journal(Component):
 | 
					class Journal(Component):
 | 
				
			||||||
| 
						 | 
					@ -1168,6 +1171,7 @@ class Journal(Component):
 | 
				
			||||||
        return timedelta(0)
 | 
					        return timedelta(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sequence = sequence_property
 | 
					    sequence = sequence_property
 | 
				
			||||||
 | 
					    categories = categories_property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FreeBusy(Component):
 | 
					class FreeBusy(Component):
 | 
				
			||||||
| 
						 | 
					@ -2101,6 +2105,7 @@ class Calendar(Component):
 | 
				
			||||||
    END:VCALENDAR
 | 
					    END:VCALENDAR
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    categories = categories_property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# These are read only singleton, so one instance is enough for the module
 | 
					# These are read only singleton, so one instance is enough for the module
 | 
				
			||||||
types_factory = TypesFactory()
 | 
					types_factory = TypesFactory()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -430,17 +430,20 @@ class vDDDLists:
 | 
				
			||||||
class vCategory:
 | 
					class vCategory:
 | 
				
			||||||
    params: Parameters
 | 
					    params: Parameters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, c_list, params={}):
 | 
					    def __init__(self, c_list:list[str] | str, params={}):
 | 
				
			||||||
        if not hasattr(c_list, "__iter__") or isinstance(c_list, str):
 | 
					        if not hasattr(c_list, "__iter__") or isinstance(c_list, str):
 | 
				
			||||||
            c_list = [c_list]
 | 
					            c_list = [c_list]
 | 
				
			||||||
        self.cats = [vText(c) for c in c_list]
 | 
					        self.cats : list[vText|str] = [vText(c) for c in c_list]
 | 
				
			||||||
        self.params = Parameters(params)
 | 
					        self.params = Parameters(params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __iter__(self):
 | 
					    def __iter__(self):
 | 
				
			||||||
        return iter(vCategory.from_ical(self.to_ical()))
 | 
					        return iter(vCategory.from_ical(self.to_ical()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_ical(self):
 | 
					    def to_ical(self):
 | 
				
			||||||
        return b",".join([c.to_ical() for c in self.cats])
 | 
					        return b",".join([
 | 
				
			||||||
 | 
					            c.to_ical() if hasattr(c, "to_ical") else vText(c).to_ical()
 | 
				
			||||||
 | 
					            for c in self.cats
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def from_ical(ical):
 | 
					    def from_ical(ical):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
"""This tests the compatibility with RFC 7529.
 | 
					"""This tests the compatibility with RFC 7529.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See
 | 
					See
 | 
				
			||||||
- https://github.com/collective/icalendar/issues/653
 | 
					- https://github.com/collective/icalendar/issues/655
 | 
				
			||||||
- https://www.rfc-editor.org/rfc/rfc7529.html
 | 
					- https://www.rfc-editor.org/rfc/rfc7529.html
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,70 @@
 | 
				
			||||||
 | 
					"""This tests the compatibility with RFC 7529.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CATEGORIES property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See
 | 
				
			||||||
 | 
					- https://github.com/collective/icalendar/issues/655
 | 
				
			||||||
 | 
					- https://www.rfc-editor.org/rfc/rfc7529.html
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from icalendar import Calendar, Event, Journal, Todo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CTJE = Union[Calendar, Event, Journal, Todo]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture(params=[Event, Calendar, Todo, Journal])
 | 
				
			||||||
 | 
					def component(request):
 | 
				
			||||||
 | 
					    """An empty component with possible categories."""
 | 
				
			||||||
 | 
					    return request.param()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_no_categories_at_creation(component: CTJE):
 | 
				
			||||||
 | 
					    """An empty component has no categories."""
 | 
				
			||||||
 | 
					    assert "CATEGORIES" not in component
 | 
				
			||||||
 | 
					    assert component.categories == []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_add_one_category(component: CTJE):
 | 
				
			||||||
 | 
					    """Add one category."""
 | 
				
			||||||
 | 
					    component.add("categories", "Lecture")
 | 
				
			||||||
 | 
					    assert component.categories == ["Lecture"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_add_multiple_categories(component: CTJE):
 | 
				
			||||||
 | 
					    """Add categories."""
 | 
				
			||||||
 | 
					    component.add("categories", ["Lecture", "Workshop"])
 | 
				
			||||||
 | 
					    assert component.categories == ["Lecture", "Workshop"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_set_categories(component: CTJE):
 | 
				
			||||||
 | 
					    """Set categories."""
 | 
				
			||||||
 | 
					    component.categories = ["Lecture", "Workshop"]
 | 
				
			||||||
 | 
					    assert component.categories == ["Lecture", "Workshop"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_modify_list(component: CTJE):
 | 
				
			||||||
 | 
					    """Modify the list and it still works."""
 | 
				
			||||||
 | 
					    component.categories = categories = ["cat1"]
 | 
				
			||||||
 | 
					    categories.append("cat2")
 | 
				
			||||||
 | 
					    assert component.categories == ["cat1", "cat2"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_delete_categories(component: CTJE):
 | 
				
			||||||
 | 
					    """Delete categories."""
 | 
				
			||||||
 | 
					    component.categories = ["Lecture", "Workshop"]
 | 
				
			||||||
 | 
					    del component.categories
 | 
				
			||||||
 | 
					    assert "CATEGORIES" not in component
 | 
				
			||||||
 | 
					    assert component.categories == []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_deal_with_several_categories(component: CTJE):
 | 
				
			||||||
 | 
					    """If we have categories several times, we should all use them."""
 | 
				
			||||||
 | 
					    component.add("categories", ["c1", "c2"])
 | 
				
			||||||
 | 
					    component.add("categories", ["c3", "c4"])
 | 
				
			||||||
 | 
					    assert component.categories == ["c1", "c2", "c3", "c4"]
 | 
				
			||||||
 | 
					    component.categories.append("c5")
 | 
				
			||||||
 | 
					    assert component.categories == ["c1", "c2", "c3", "c4", "c5"]
 | 
				
			||||||
 | 
					    component.categories.remove("c2")
 | 
				
			||||||
 | 
					    assert component.categories == ["c1", "c3", "c4", "c5"]
 | 
				
			||||||
 | 
					    assert "c1,c3,c4,c5" in component.to_ical().decode()
 | 
				
			||||||
		Ładowanie…
	
		Reference in New Issue