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