77import csv
88from typing import TYPE_CHECKING
99
10+ from django .conf import settings
1011from django .contrib .auth .decorators import login_required
1112from django .core .exceptions import PermissionDenied
1213from django .http import HttpResponse
1314from django .shortcuts import get_object_or_404 , redirect
1415from django .urls import reverse
1516from django .urls .exceptions import NoReverseMatch
16- from django .utils .translation import activate , gettext , pgettext
17+ from django .utils import feedgenerator
18+ from django .utils .translation import activate , get_language , gettext , pgettext
1719from django .views .generic .list import ListView
1820
1921from weblate .accounts .notifications import NOTIFICATIONS_ACTIONS
@@ -45,6 +47,44 @@ class ChangesView(PathViewMixin, ListView):
4547 Unit ,
4648 )
4749
50+ def get_changes_url (self , url_name : str = "changes" ) -> str :
51+ if self .path_object is None :
52+ return reverse (url_name )
53+ return reverse (url_name , kwargs = {"path" : self .path_object .get_url_path ()})
54+
55+ def get_filtered_changes_url (self ) -> str :
56+ url = self .get_changes_url ()
57+ if self .changes_form .is_valid () and (
58+ query_string := self .changes_form .urlencode ()
59+ ):
60+ return f"{ url } ?{ query_string } "
61+ return url
62+
63+ def get_title (self ):
64+ if isinstance (self .path_object , Unit ):
65+ return (
66+ pgettext (
67+ "Changes of string in a translation" , "Changes of string in %s"
68+ )
69+ % self .path_object
70+ )
71+ if isinstance (self .path_object , Translation ):
72+ return (
73+ pgettext ("Changes in translation" , "Changes in %s" ) % self .path_object
74+ )
75+ if isinstance (self .path_object , Component ):
76+ return pgettext ("Changes in component" , "Changes in %s" ) % self .path_object
77+ if isinstance (self .path_object , Project ):
78+ return pgettext ("Changes in project" , "Changes in %s" ) % self .path_object
79+ if isinstance (self .path_object , Language ):
80+ return pgettext ("Changes in language" , "Changes in %s" ) % self .path_object
81+ if isinstance (self .path_object , ProjectLanguage ):
82+ return pgettext ("Changes in project" , "Changes in %s" ) % self .path_object
83+ if self .path_object is None :
84+ return gettext ("Changes" )
85+ msg = f"Unsupported { self .path_object } "
86+ raise TypeError (msg )
87+
4888 def get_template_names (self ):
4989 if digest := self .request .GET .get ("digest" ):
5090 if digest in {"pending_suggestions" , "todo_strings" }:
@@ -56,46 +96,8 @@ def get_context_data(self, **kwargs):
5696 """Create context for rendering page."""
5797 context = super ().get_context_data (** kwargs )
5898 context ["path_object" ] = self .path_object
59-
60- if isinstance (self .path_object , Unit ):
61- context ["title" ] = (
62- pgettext (
63- "Changes of string in a translation" , "Changes of string in %s"
64- )
65- % self .path_object
66- )
67- elif isinstance (self .path_object , Translation ):
68- context ["title" ] = (
69- pgettext ("Changes in translation" , "Changes in %s" ) % self .path_object
70- )
71- elif isinstance (self .path_object , Component ):
72- context ["title" ] = (
73- pgettext ("Changes in component" , "Changes in %s" ) % self .path_object
74- )
75- elif isinstance (self .path_object , Project ):
76- context ["title" ] = (
77- pgettext ("Changes in project" , "Changes in %s" ) % self .path_object
78- )
79- elif isinstance (self .path_object , Language ):
80- context ["title" ] = (
81- pgettext ("Changes in language" , "Changes in %s" ) % self .path_object
82- )
83- elif isinstance (self .path_object , ProjectLanguage ):
84- context ["title" ] = (
85- pgettext ("Changes in project" , "Changes in %s" ) % self .path_object
86- )
87- elif self .path_object is None :
88- context ["title" ] = gettext ("Changes" )
89- else :
90- msg = f"Unsupported { self .path_object } "
91- raise TypeError (msg )
92-
93- if self .path_object is None :
94- context ["changes_rss" ] = reverse ("rss" )
95- else :
96- context ["changes_rss" ] = reverse (
97- "rss" , kwargs = {"path" : self .path_object .get_url_path ()}
98- )
99+ context ["title" ] = self .get_title ()
100+ context ["changes_rss" ] = self .get_changes_url ("changes-rss" )
99101
100102 if self .changes_form .is_valid ():
101103 context ["query_string" ] = self .changes_form .urlencode ()
@@ -126,7 +128,9 @@ def get_request_param(self, request: AuthenticatedHttpRequest, param: str) -> st
126128 return "-"
127129 return value
128130
129- def get (self , request : AuthenticatedHttpRequest , * args , ** kwargs ): # type: ignore[override]
131+ def get ( # type: ignore[override]
132+ self , request : AuthenticatedHttpRequest , * args , ** kwargs
133+ ):
130134 if self .path_object is None and self .request .GET :
131135 # Handle GET params for filtering prior Weblate 5.0
132136 path = None
@@ -220,7 +224,9 @@ class ChangesCSVView(ChangesView):
220224
221225 paginate_by = None
222226
223- def get (self , request : AuthenticatedHttpRequest , * args , ** kwargs ): # type: ignore[override]
227+ def get ( # type: ignore[override]
228+ self , request : AuthenticatedHttpRequest , * args , ** kwargs
229+ ):
224230 object_list = self .get_queryset ()[:2000 ]
225231
226232 if not request .user .has_perm ("change.download" , self .path_object ):
@@ -254,6 +260,65 @@ def get(self, request: AuthenticatedHttpRequest, *args, **kwargs): # type: igno
254260 return response
255261
256262
263+ class ChangesRSSView (ChangesView ):
264+ """RSS renderer for changes view."""
265+
266+ feed_count = 10
267+ paginate_by = None
268+
269+ def get_feed_title (self ):
270+ if self .path_object is None :
271+ # Translators: %s is site title here
272+ return gettext ("Recent changes on %s" ) % settings .SITE_TITLE
273+ # Translators: %s is translation/project/component/language name
274+ return gettext ("Recent changes in %s" ) % self .path_object
275+
276+ def get_feed_description (self ):
277+ if self .path_object is None :
278+ # Translators: %s is site title here
279+ return gettext ("All recent changes made using Weblate on %s." ) % (
280+ settings .SITE_TITLE
281+ )
282+ # Translators: %s is translation/project/component/language name
283+ return (
284+ gettext ("All recent changes made using Weblate in %s." ) % self .path_object
285+ )
286+
287+ def get ( # type: ignore[override]
288+ self , request : AuthenticatedHttpRequest , * args , ** kwargs
289+ ):
290+ if self .changes_form .is_valid ():
291+ object_list = Change .objects .preload_list (
292+ list (self .get_queryset ()[: self .feed_count ])
293+ )
294+ else :
295+ object_list = []
296+
297+ feed = feedgenerator .Rss201rev2Feed (
298+ title = self .get_feed_title (),
299+ link = get_site_url (self .get_filtered_changes_url ()),
300+ description = self .get_feed_description (),
301+ language = get_language (),
302+ feed_url = get_site_url (request .get_full_path ()),
303+ )
304+
305+ for change in object_list :
306+ link = get_site_url (change .get_absolute_url ())
307+ feed .add_item (
308+ title = change .get_action_display (),
309+ link = link ,
310+ description = str (change ),
311+ author_name = change .get_user_display (False ),
312+ pubdate = change .timestamp ,
313+ unique_id = link ,
314+ unique_id_is_permalink = True ,
315+ )
316+
317+ response = HttpResponse (content_type = feed .content_type )
318+ feed .write (response , "utf-8" )
319+ return response
320+
321+
257322@login_required
258323def show_change (request : AuthenticatedHttpRequest , pk : int ):
259324 change = get_object_or_404 (Change , pk = pk )
0 commit comments