diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3945219a..fd0a6a8f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -60,6 +60,7 @@ Changed
- Raise ``update_cache()`` exception for ``synccache`` in debug mode (#1375)
- **Timeline**
- Update ``get_object_link()`` usage for ``PluginObjectLink`` return data (#1343)
+ - Rename ``ProjectEvent*`` models to ``TimelineEvent*`` (#1414)
- **Userprofile**
- Disable global user settings on target site in ``UserSettingsForm`` (#1329)
diff --git a/docs/source/dev_project_app.rst b/docs/source/dev_project_app.rst
index b49eda59..165b1bc5 100644
--- a/docs/source/dev_project_app.rst
+++ b/docs/source/dev_project_app.rst
@@ -905,8 +905,8 @@ with the name of your application as specified in its ``ProjectAppPlugin``.
.. code-block:: python
- from timeline.models import ProjectEvent
- ProjectEvent.objects.filter(app='app_name').delete()
+ from timeline.models import TimelineEvent
+ TimelineEvent.objects.filter(app='app_name').delete()
Next you should delete existing database objects defined by the models in your
app. This is also most easily done via the Django shell. Example:
diff --git a/docs/source/major_changes.rst b/docs/source/major_changes.rst
index 20f7c35b..8912b7b5 100644
--- a/docs/source/major_changes.rst
+++ b/docs/source/major_changes.rst
@@ -27,6 +27,7 @@ Release Highlights
- Rewrite sodarcache REST API views
- Rename AppSettingAPI "app_name" arguments to "plugin_name"
- Plugin API return data updates and deprecations
+- Rename timeline app models
- Rename base test classes
- Remove Python v3.8 support
- Remove SAML SSO support
@@ -153,6 +154,18 @@ in this release:
REST API conventions regarding URLs and return data. See the API
documentation and refactor your client code accordingly.
+Timeline Models Renamed
+-----------------------
+
+Models in the timeline app have been renamed from ``ProjectEvent*`` to
+``TimelineEvent*``. This is done to better reflect the fact that events are not
+necessarily tied to projects.
+
+If your site only accesses these models through ``TimelineAPI`` and
+``TimelineAPI.get_model()``, which is the strongly recommended way, no changes
+should be required.
+
+
AppSettingAPI Plugin Name Arguments Updated
-------------------------------------------
diff --git a/projectroles/tests/test_views.py b/projectroles/tests/test_views.py
index 587e92a9..37d05782 100644
--- a/projectroles/tests/test_views.py
+++ b/projectroles/tests/test_views.py
@@ -16,10 +16,10 @@
from test_plus.test import TestCase
# Timeline dependency
-from timeline.models import ProjectEvent
+from timeline.models import TimelineEvent
from timeline.tests.test_models import (
- ProjectEventMixin,
- ProjectEventStatusMixin,
+ TimelineEventMixin,
+ TimelineEventStatusMixin,
)
from projectroles.app_settings import AppSettingAPI
@@ -270,8 +270,8 @@ class TestProjectSearchResultsView(
ProjectMixin,
RoleAssignmentMixin,
ViewTestBase,
- ProjectEventMixin,
- ProjectEventStatusMixin,
+ TimelineEventMixin,
+ TimelineEventStatusMixin,
):
"""Tests for ProjectSearchResultsView"""
@@ -1409,11 +1409,11 @@ class TestProjectArchiveView(
@classmethod
def _get_tl(cls):
- return ProjectEvent.objects.filter(event_name='project_archive')
+ return TimelineEvent.objects.filter(event_name='project_archive')
@classmethod
def _get_tl_un(cls):
- return ProjectEvent.objects.filter(event_name='project_unarchive')
+ return TimelineEvent.objects.filter(event_name='project_unarchive')
def _get_alerts(self):
return self.app_alert_model.objects.filter(alert_name='project_archive')
@@ -4323,7 +4323,7 @@ def test_get_role_exists(self):
self.make_assignment(self.project, invited_user, self.role_guest)
self.assertTrue(invite.active)
self.assertIsNone(
- ProjectEvent.objects.filter(event_name='invite_accept').first()
+ TimelineEvent.objects.filter(event_name='invite_accept').first()
)
with self.login(invited_user):
@@ -4339,7 +4339,7 @@ def test_get_role_exists(self):
self.assertFalse(invite.active)
# No timeline event should be created
self.assertIsNone(
- ProjectEvent.objects.filter(event_name='invite_accept').first()
+ TimelineEvent.objects.filter(event_name='invite_accept').first()
)
@@ -4548,7 +4548,7 @@ def test_post_source(self):
"""Test RemoteSiteCreateView POST as source site"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='target_site_create'
).count(),
)
@@ -4578,7 +4578,7 @@ def test_post_source(self):
model_dict = model_to_dict(site)
self.assertEqual(model_dict, expected)
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='target_site_create'
).first()
self.assertEqual(tl_event.event_name, 'target_site_create')
@@ -4590,7 +4590,7 @@ def test_post_target(self):
"""Test POST as target"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(event_name='source_site_set').count(),
+ TimelineEvent.objects.filter(event_name='source_site_set').count(),
)
self.assertEqual(RemoteSite.objects.all().count(), 0)
values = {
@@ -4619,7 +4619,7 @@ def test_post_target(self):
model_dict = model_to_dict(site)
self.assertEqual(model_dict, expected)
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='source_site_set'
).first()
self.assertEqual(tl_event.event_name, 'source_site_set')
@@ -4695,7 +4695,7 @@ def test_post(self):
"""Test RemoteSiteUpdateView POST as source"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='target_site_update'
).count(),
)
@@ -4725,7 +4725,7 @@ def test_post(self):
model_dict = model_to_dict(site)
self.assertEqual(model_dict, expected)
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='target_site_update'
).first()
self.assertEqual(tl_event.event_name, 'target_site_update')
@@ -4800,7 +4800,7 @@ def test_post(self):
"""Test RemoteSiteDeleteView POST"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='target_site_delete'
).count(),
)
@@ -4808,7 +4808,7 @@ def test_post(self):
with self.login(self.user):
response = self.client.post(self.url)
self.assertRedirects(response, reverse('projectroles:remote_sites'))
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='target_site_delete'
).first()
self.assertEqual(tl_event.event_name, 'target_site_delete')
@@ -4857,7 +4857,7 @@ def test_post_confirm(self):
response = self.client.post(self.url, values)
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='remote_access_update'
).count(),
)
@@ -4881,7 +4881,7 @@ def test_post_confirm_no_change(self):
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='remote_access_update'
).count(),
)
@@ -4890,7 +4890,7 @@ def test_post_create(self):
"""Test POST to create new RemoteProject"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='remote_batch_update'
).count(),
)
@@ -4915,7 +4915,7 @@ def test_post_create(self):
self.assertEqual(rp.project_uuid, self.project.sodar_uuid)
self.assertEqual(rp.level, SODAR_CONSTANTS['REMOTE_LEVEL_READ_INFO'])
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='remote_batch_update'
).first()
self.assertEqual(tl_event.event_name, 'remote_batch_update')
@@ -4924,7 +4924,7 @@ def test_post_update(self):
"""Test POST to update existing RemoteProject"""
self.assertEqual(
0,
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='remote_batch_update'
).count(),
)
@@ -4955,7 +4955,7 @@ def test_post_update(self):
self.assertEqual(rp.project_uuid, self.project.sodar_uuid)
self.assertEqual(rp.level, SODAR_CONSTANTS['REMOTE_LEVEL_READ_INFO'])
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='remote_batch_update'
).first()
self.assertEqual(tl_event.event_name, 'remote_batch_update')
diff --git a/projectroles/tests/test_views_api.py b/projectroles/tests/test_views_api.py
index ef46de64..0f181e2e 100644
--- a/projectroles/tests/test_views_api.py
+++ b/projectroles/tests/test_views_api.py
@@ -16,7 +16,7 @@
from test_plus.test import APITestCase
# Timeline dependency
-from timeline.models import ProjectEvent
+from timeline.models import TimelineEvent
from projectroles import views_api
from projectroles.app_settings import AppSettingAPI
@@ -2827,7 +2827,7 @@ def test_post_project(self):
"""Test TestProjectSettingSetAPIView POST with PROJECT scope setting"""
self.assertEqual(AppSetting.objects.count(), 0)
self.assertIsNone(
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='app_setting_set_api'
).first()
)
@@ -2843,7 +2843,7 @@ def test_post_project(self):
self.assertEqual(AppSetting.objects.count(), 1)
obj = AppSetting.objects.get(name=setting_name, project=self.project)
self.assertEqual(obj.get_value(), 'value')
- tl_event = ProjectEvent.objects.filter(
+ tl_event = TimelineEvent.objects.filter(
event_name='app_setting_set_api'
).first()
self.assertIsNotNone(tl_event)
@@ -3078,7 +3078,7 @@ def test_post(self):
"""Test UserSettingSetAPIView POST"""
self.assertEqual(AppSetting.objects.count(), 0)
self.assertIsNone(
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='app_setting_set_api'
).first()
)
@@ -3095,7 +3095,7 @@ def test_post(self):
obj = AppSetting.objects.get(name=setting_name, user=self.user)
self.assertEqual(obj.get_value(), 'value')
self.assertIsNone(
- ProjectEvent.objects.filter(
+ TimelineEvent.objects.filter(
event_name='app_setting_set_api'
).first()
)
diff --git a/timeline/admin.py b/timeline/admin.py
index c58d1227..44efb3e4 100644
--- a/timeline/admin.py
+++ b/timeline/admin.py
@@ -1,8 +1,8 @@
from django.contrib import admin
-from .models import ProjectEvent, ProjectEventObjectRef, ProjectEventStatus
+from .models import TimelineEvent, TimelineEventObjectRef, TimelineEventStatus
-admin.site.register(ProjectEvent)
-admin.site.register(ProjectEventObjectRef)
-admin.site.register(ProjectEventStatus)
+admin.site.register(TimelineEvent)
+admin.site.register(TimelineEventObjectRef)
+admin.site.register(TimelineEventStatus)
diff --git a/timeline/api.py b/timeline/api.py
index 8a25d8f0..25183533 100644
--- a/timeline/api.py
+++ b/timeline/api.py
@@ -15,8 +15,8 @@
from projectroles.utils import get_app_names
from timeline.models import (
- ProjectEvent,
- ProjectEventObjectRef,
+ TimelineEvent,
+ TimelineEventObjectRef,
EVENT_STATUS_TYPES,
OBJ_REF_UNNAMED,
)
@@ -123,7 +123,7 @@ def _get_ref_description(cls, event, ref_label, app_plugin, request):
Get reference object description for event description, or unknown label
if not found.
- :param event: ProjectEvent object
+ :param event: TimelineEvent object
:param ref_label: Label for the reference object (string)
:param app_plugin: App plugin or None
:param request: Request object or None
@@ -136,10 +136,10 @@ def _get_ref_description(cls, event, ref_label, app_plugin, request):
# Get object reference
try:
- obj_ref = ProjectEventObjectRef.objects.get(
+ obj_ref = TimelineEventObjectRef.objects.get(
event=event, label=ref_label
)
- except ProjectEventObjectRef.DoesNotExist:
+ except TimelineEventObjectRef.DoesNotExist:
return UNKNOWN_LABEL
# Special case: User model
@@ -238,7 +238,7 @@ def add_event(
:param status_extra_data: Extra data for initial status (dict, optional)
:param plugin_name: Name of plugin to which the event is related
(optional, plugin with the name of the app is assumed if unset)
- :return: ProjectEvent object
+ :return: TimelineEvent object
:raise: ValueError if app_name or status_type is invalid
"""
if app_name not in APP_NAMES:
@@ -258,7 +258,7 @@ def add_event(
if user and user.is_anonymous:
user = None
- event = ProjectEvent()
+ event = TimelineEvent()
event.project = project
event.app = app_name
event.plugin = plugin_name
@@ -287,7 +287,7 @@ def get_project_events(cls, project, classified=False):
:param classified: Include classified (boolean)
:return: QuerySet
"""
- events = ProjectEvent.objects.filter(project=project)
+ events = TimelineEvent.objects.filter(project=project)
if not classified:
events = events.filter(classified=False)
return events
@@ -297,7 +297,7 @@ def get_event_description(cls, event, plugin_lookup=None, request=None):
"""
Return the description of a timeline event as HTML.
- :param event: ProjectEvent object
+ :param event: TimelineEvent object
:param plugin_lookup: App plugin lookup dict (optional)
:param request: Request object (optional)
:return: String (contains HTML)
@@ -376,8 +376,8 @@ def get_object_link(cls, obj, project=None):
@classmethod
def get_models(cls):
"""
- Return project event model classes for custom/advanced queries.
+ Return timeline event model classes for custom/advanced queries.
- :return: ProjectEvent, ProjectEventObjectRef
+ :return: TimelineEvent, TimelineEventObjectRef
"""
- return ProjectEvent, ProjectEventObjectRef
+ return TimelineEvent, TimelineEventObjectRef
diff --git a/timeline/migrations/0012_rename_models.py b/timeline/migrations/0012_rename_models.py
new file mode 100644
index 00000000..1316e6ff
--- /dev/null
+++ b/timeline/migrations/0012_rename_models.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.11 on 2024-04-17 15:55
+
+from django.conf import settings
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("projectroles", "0028_populate_finder_role"),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("timeline", "0011_make_uuid_unique"),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name="ProjectEvent",
+ new_name="TimelineEvent",
+ ),
+ migrations.RenameModel(
+ old_name="ProjectEventObjectRef",
+ new_name="TimelineEventObjectRef",
+ ),
+ migrations.RenameModel(
+ old_name="ProjectEventStatus",
+ new_name="TimelineEventStatus",
+ ),
+ ]
diff --git a/timeline/models.py b/timeline/models.py
index 100cecf7..f17b9a9b 100644
--- a/timeline/models.py
+++ b/timeline/models.py
@@ -29,8 +29,8 @@
OBJ_REF_UNNAMED = '(unnamed)'
-class ProjectEventManager(models.Manager):
- """Manager for custom table-level ProjectEvent queries"""
+class TimelineEventManager(models.Manager):
+ """Manager for custom table-level TimelineEvent queries"""
def get_object_events(
self, project, object_model, object_uuid, order_by='-pk'
@@ -44,7 +44,7 @@ def get_object_events(
:param order_by: Ordering (default = pk descending)
:return: QuerySet
"""
- return ProjectEvent.objects.filter(
+ return TimelineEvent.objects.filter(
project=project,
event_objects__object_model=object_model,
event_objects__object_uuid=object_uuid,
@@ -56,7 +56,7 @@ def find(self, search_terms, keywords=None):
:param search_terms: Search terms (list of strings)
:param keywords: Optional search keywords as key/value pairs (dict)
- :return: QuerySet of ProjectEvent objects
+ :return: QuerySet of TimelineEvent objects
"""
search_limit = getattr(settings, 'TIMELINE_SEARCH_LIMIT', 250)
term_query = Q()
@@ -73,7 +73,7 @@ def find(self, search_terms, keywords=None):
return items[:search_limit]
-class ProjectEvent(models.Model):
+class TimelineEvent(models.Model):
"""
Class representing a Project event. Can also be a site-wide event not linked
to a specific project.
@@ -137,7 +137,7 @@ class ProjectEvent(models.Model):
)
# Set manager for custom queries
- objects = ProjectEventManager()
+ objects = TimelineEventManager()
def __str__(self):
return '{}{}{}'.format(
@@ -147,7 +147,7 @@ def __str__(self):
)
def __repr__(self):
- return 'ProjectEvent({})'.format(
+ return 'TimelineEvent({})'.format(
', '.join(repr(v) for v in self.get_repr_values())
)
@@ -180,9 +180,9 @@ def add_object(self, obj, label, name, extra_data=None):
:param label: Label for the object in the event description (string)
:param name: Name or title of the object (string)
:param extra_data: Additional data related to object (dict, optional)
- :return: ProjectEventObjectRef object
+ :return: TimelineEventObjectRef object
"""
- ref = ProjectEventObjectRef()
+ ref = TimelineEventObjectRef()
ref.event = self
ref.label = label
if not name:
@@ -207,7 +207,7 @@ def set_status(self, status_type, status_desc=None, extra_data=None):
:param status_type: Status type string (see EVENT_STATUS_TYPES)
:param status_desc: Description string (optional)
:param extra_data: Extra data for the status (dict, optional)
- :return: ProjectEventStatus object
+ :return: TimelineEventStatus object
:raise: TypeError if status_type is invalid
"""
if status_type not in EVENT_STATUS_TYPES:
@@ -217,7 +217,7 @@ def set_status(self, status_type, status_desc=None, extra_data=None):
)
)
- status = ProjectEventStatus()
+ status = TimelineEventStatus()
status.event = self
status.status_type = status_type
status.description = (
@@ -229,13 +229,13 @@ def set_status(self, status_type, status_desc=None, extra_data=None):
return status
-class ProjectEventObjectRef(models.Model):
+class TimelineEventObjectRef(models.Model):
"""Class representing a reference to an object (existing or removed)
related to a Timeline event status"""
#: Event to which the object belongs
event = models.ForeignKey(
- ProjectEvent,
+ TimelineEvent,
related_name='event_objects',
help_text='Event to which the object belongs',
on_delete=models.CASCADE,
@@ -283,17 +283,17 @@ def __str__(self):
def __repr__(self):
values = self.event.get_repr_values() + [self.name]
- return 'ProjectEventObjectRef({})'.format(
+ return 'TimelineEventObjectRef({})'.format(
', '.join(repr(v) for v in values)
)
-class ProjectEventStatus(models.Model):
+class TimelineEventStatus(models.Model):
"""Class representing a Timeline event status"""
#: Event to which the status change belongs
event = models.ForeignKey(
- ProjectEvent,
+ TimelineEvent,
related_name='status_changes',
help_text='Event to which the status change belongs',
on_delete=models.CASCADE,
@@ -335,7 +335,7 @@ def __str__(self):
def __repr__(self):
values = self.event.get_repr_values() + [self.status_type]
- return 'ProjectEventStatus({})'.format(
+ return 'TimelineEventStatus({})'.format(
', '.join(repr(v) for v in values)
)
diff --git a/timeline/plugins.py b/timeline/plugins.py
index 9009ea17..8dd0a1a9 100644
--- a/timeline/plugins.py
+++ b/timeline/plugins.py
@@ -11,7 +11,7 @@
from projectroles.utils import get_display_name
from timeline.api import TimelineAPI
-from timeline.models import ProjectEvent
+from timeline.models import TimelineEvent
from timeline.urls import urls_ui_project, urls_ui_site, urls_ui_admin
@@ -73,7 +73,7 @@ def get_statistics(self):
return {
'event_count': {
'label': 'Events',
- 'value': ProjectEvent.objects.all().count(),
+ 'value': TimelineEvent.objects.all().count(),
}
}
@@ -106,7 +106,7 @@ def search(self, search_terms, user, search_type=None, keywords=None):
"""
items = []
if not search_type or search_type == 'timeline':
- events = ProjectEvent.objects.find(search_terms, keywords)
+ events = TimelineEvent.objects.find(search_terms, keywords)
if events:
items = [
event
diff --git a/timeline/templates/timeline/_list_item.html b/timeline/templates/timeline/_list_item.html
index da757ecd..a9eba480 100644
--- a/timeline/templates/timeline/_list_item.html
+++ b/timeline/templates/timeline/_list_item.html
@@ -10,12 +10,12 @@
{% get_timestamp event as event_time %}
{% if event.project %}
+ data-url="{% url 'timeline:ajax_detail_project' timelineevent=event.sodar_uuid %}">
{{ event_time }}
{% else %}
+ data-url="{% url 'timeline:ajax_detail_site' timelineevent=event.sodar_uuid %}">
{{ event_time }}
{% endif %}
@@ -46,14 +46,14 @@
{% if not details_card_mode and event.extra_data %}
{% if event.project and can_view_extra_data %}
{% elif not event.project and user.is_superuser %}