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 ``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>`_. | ||||
| 
 | ||||
| Bug fixes: | ||||
|  |  | |||
|  | @ -2,10 +2,11 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from datetime import date, datetime | ||||
| import itertools | ||||
| from typing import TYPE_CHECKING, Optional | ||||
| 
 | ||||
| from icalendar.error import InvalidCalendar | ||||
| from icalendar.prop import vDDDTypes, vText | ||||
| from icalendar.prop import vCategory, vDDDTypes, vText | ||||
| from icalendar.timezone import tzp | ||||
| 
 | ||||
| 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__ = [ | ||||
|     "single_utc_property", | ||||
|     "multi_language_text_property", | ||||
|     "single_int_property", | ||||
|     "sequence_property" | ||||
|     "sequence_property", | ||||
|     "categories_property", | ||||
| ] | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import dateutil.rrule | |||
| import dateutil.tz | ||||
| 
 | ||||
| from icalendar.attr import ( | ||||
|     categories_property, | ||||
|     multi_language_text_property, | ||||
|     sequence_property, | ||||
|     single_int_property, | ||||
|  | @ -894,6 +895,7 @@ class Event(Component): | |||
|     X_MOZ_SNOOZE_TIME = _X_MOZ_SNOOZE_TIME | ||||
|     X_MOZ_LASTACK = _X_MOZ_LASTACK | ||||
|     sequence = sequence_property | ||||
|     categories = categories_property | ||||
| 
 | ||||
| 
 | ||||
| class Todo(Component): | ||||
|  | @ -1083,6 +1085,7 @@ class Todo(Component): | |||
|         return Alarms(self) | ||||
| 
 | ||||
|     sequence = sequence_property | ||||
|     categories = categories_property | ||||
| 
 | ||||
| 
 | ||||
| class Journal(Component): | ||||
|  | @ -1168,6 +1171,7 @@ class Journal(Component): | |||
|         return timedelta(0) | ||||
| 
 | ||||
|     sequence = sequence_property | ||||
|     categories = categories_property | ||||
| 
 | ||||
| 
 | ||||
| class FreeBusy(Component): | ||||
|  | @ -2101,6 +2105,7 @@ class Calendar(Component): | |||
|     END:VCALENDAR | ||||
|     """ | ||||
|     ) | ||||
|     categories = categories_property | ||||
| 
 | ||||
| # These are read only singleton, so one instance is enough for the module | ||||
| types_factory = TypesFactory() | ||||
|  |  | |||
|  | @ -430,17 +430,20 @@ class vDDDLists: | |||
| class vCategory: | ||||
|     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): | ||||
|             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) | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return iter(vCategory.from_ical(self.to_ical())) | ||||
| 
 | ||||
|     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 | ||||
|     def from_ical(ical): | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| """This tests the compatibility with RFC 7529. | ||||
| 
 | ||||
| See | ||||
| - https://github.com/collective/icalendar/issues/653 | ||||
| - https://github.com/collective/icalendar/issues/655 | ||||
| - 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
	
	 Nicco Kunzmann
						Nicco Kunzmann