mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add news reader (#3445)
* add model for feed entries * add task to update feed entries * Add API routes * Fix name in model * rename model * fix read endpoint * reduce duplication in NewsFeed API endpoints * reduce duplicated code * add ui elements to index * add missing migrations * add ressource route * add new model to admin * reorder fields * format timestamp * make title linked * reduce migrations to 1 * fix merge * fix js style * add model to ruleset
This commit is contained in:
parent
f6cfc12343
commit
fb77158496
@ -26,6 +26,8 @@ from sentry_sdk.integrations.django import DjangoIntegration
|
|||||||
from . import config
|
from . import config
|
||||||
from .config import get_boolean_setting, get_custom_file, get_setting
|
from .config import get_boolean_setting, get_custom_file, get_setting
|
||||||
|
|
||||||
|
INVENTREE_NEWS_URL = 'https://inventree.org/news/feed.atom'
|
||||||
|
|
||||||
# Determine if we are running in "test" mode e.g. "manage.py test"
|
# Determine if we are running in "test" mode e.g. "manage.py test"
|
||||||
TESTING = 'test' in sys.argv
|
TESTING = 'test' in sys.argv
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ translated_javascript_urls = [
|
|||||||
re_path(r'^search.js', DynamicJsView.as_view(template_name='js/translated/search.js'), name='search.js'),
|
re_path(r'^search.js', DynamicJsView.as_view(template_name='js/translated/search.js'), name='search.js'),
|
||||||
re_path(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'),
|
re_path(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'),
|
||||||
re_path(r'^plugin.js', DynamicJsView.as_view(template_name='js/translated/plugin.js'), name='plugin.js'),
|
re_path(r'^plugin.js', DynamicJsView.as_view(template_name='js/translated/plugin.js'), name='plugin.js'),
|
||||||
|
re_path(r'^news.js', DynamicJsView.as_view(template_name='js/translated/news.js'), name='news.js'),
|
||||||
re_path(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'),
|
re_path(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'),
|
||||||
re_path(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'),
|
re_path(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'),
|
||||||
re_path(r'^notification.js', DynamicJsView.as_view(template_name='js/translated/notification.js'), name='notification.js'),
|
re_path(r'^notification.js', DynamicJsView.as_view(template_name='js/translated/notification.js'), name='notification.js'),
|
||||||
|
@ -55,9 +55,16 @@ class NotificationMessageAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ('name', 'category', 'message', )
|
search_fields = ('name', 'category', 'message', )
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntryAdmin(admin.ModelAdmin):
|
||||||
|
"""Admin settings for NewsFeedEntry."""
|
||||||
|
|
||||||
|
list_display = ('title', 'author', 'published', 'summary', )
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(common.models.InvenTreeSetting, SettingsAdmin)
|
admin.site.register(common.models.InvenTreeSetting, SettingsAdmin)
|
||||||
admin.site.register(common.models.InvenTreeUserSetting, UserSettingsAdmin)
|
admin.site.register(common.models.InvenTreeUserSetting, UserSettingsAdmin)
|
||||||
admin.site.register(common.models.WebhookEndpoint, WebhookAdmin)
|
admin.site.register(common.models.WebhookEndpoint, WebhookAdmin)
|
||||||
admin.site.register(common.models.WebhookMessage, ImportExportModelAdmin)
|
admin.site.register(common.models.WebhookMessage, ImportExportModelAdmin)
|
||||||
admin.site.register(common.models.NotificationEntry, NotificationEntryAdmin)
|
admin.site.register(common.models.NotificationEntry, NotificationEntryAdmin)
|
||||||
admin.site.register(common.models.NotificationMessage, NotificationMessageAdmin)
|
admin.site.register(common.models.NotificationMessage, NotificationMessageAdmin)
|
||||||
|
admin.site.register(common.models.NewsFeedEntry, NewsFeedEntryAdmin)
|
||||||
|
@ -11,6 +11,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|||||||
from django_q.tasks import async_task
|
from django_q.tasks import async_task
|
||||||
from rest_framework import filters, permissions, serializers
|
from rest_framework import filters, permissions, serializers
|
||||||
from rest_framework.exceptions import NotAcceptable, NotFound
|
from rest_framework.exceptions import NotAcceptable, NotFound
|
||||||
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
@ -255,21 +256,20 @@ class NotificationUserSettingsDetail(RetrieveUpdateAPI):
|
|||||||
|
|
||||||
queryset = NotificationUserSetting.objects.all()
|
queryset = NotificationUserSetting.objects.all()
|
||||||
serializer_class = NotificationUserSettingSerializer
|
serializer_class = NotificationUserSettingSerializer
|
||||||
|
permission_classes = [UserSettingsPermissions, ]
|
||||||
permission_classes = [
|
|
||||||
UserSettingsPermissions,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationList(BulkDeleteMixin, ListAPI):
|
class NotificationMessageMixin:
|
||||||
"""List view for all notifications of the current user."""
|
"""Generic mixin for NotificationMessage."""
|
||||||
|
|
||||||
queryset = common.models.NotificationMessage.objects.all()
|
queryset = common.models.NotificationMessage.objects.all()
|
||||||
serializer_class = common.serializers.NotificationMessageSerializer
|
serializer_class = common.serializers.NotificationMessageSerializer
|
||||||
|
permission_classes = [UserSettingsPermissions, ]
|
||||||
|
|
||||||
permission_classes = [
|
|
||||||
permissions.IsAuthenticated,
|
class NotificationList(NotificationMessageMixin, BulkDeleteMixin, ListAPI):
|
||||||
]
|
"""List view for all notifications of the current user."""
|
||||||
|
|
||||||
|
permission_classes = [permissions.IsAuthenticated, ]
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
@ -312,29 +312,16 @@ class NotificationList(BulkDeleteMixin, ListAPI):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class NotificationDetail(RetrieveUpdateDestroyAPI):
|
class NotificationDetail(NotificationMessageMixin, RetrieveUpdateDestroyAPI):
|
||||||
"""Detail view for an individual notification object.
|
"""Detail view for an individual notification object.
|
||||||
|
|
||||||
- User can only view / delete their own notification objects
|
- User can only view / delete their own notification objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = common.models.NotificationMessage.objects.all()
|
|
||||||
serializer_class = common.serializers.NotificationMessageSerializer
|
|
||||||
permission_classes = [
|
|
||||||
UserSettingsPermissions,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
class NotificationReadEdit(NotificationMessageMixin, CreateAPI):
|
||||||
class NotificationReadEdit(CreateAPI):
|
|
||||||
"""General API endpoint to manipulate read state of a notification."""
|
"""General API endpoint to manipulate read state of a notification."""
|
||||||
|
|
||||||
queryset = common.models.NotificationMessage.objects.all()
|
|
||||||
serializer_class = common.serializers.NotificationReadSerializer
|
|
||||||
|
|
||||||
permission_classes = [
|
|
||||||
UserSettingsPermissions,
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
"""Add instance to context so it can be accessed in the serializer."""
|
"""Add instance to context so it can be accessed in the serializer."""
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
@ -362,15 +349,9 @@ class NotificationUnread(NotificationReadEdit):
|
|||||||
target = False
|
target = False
|
||||||
|
|
||||||
|
|
||||||
class NotificationReadAll(RetrieveAPI):
|
class NotificationReadAll(NotificationMessageMixin, RetrieveAPI):
|
||||||
"""API endpoint to mark all notifications as read."""
|
"""API endpoint to mark all notifications as read."""
|
||||||
|
|
||||||
queryset = common.models.NotificationMessage.objects.all()
|
|
||||||
|
|
||||||
permission_classes = [
|
|
||||||
UserSettingsPermissions,
|
|
||||||
]
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Set all messages for the current user as read."""
|
"""Set all messages for the current user as read."""
|
||||||
try:
|
try:
|
||||||
@ -380,6 +361,40 @@ class NotificationReadAll(RetrieveAPI):
|
|||||||
raise serializers.ValidationError(detail=serializers.as_serializer_error(exc))
|
raise serializers.ValidationError(detail=serializers.as_serializer_error(exc))
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedMixin:
|
||||||
|
"""Generic mixin for NewsFeedEntry."""
|
||||||
|
queryset = common.models.NewsFeedEntry.objects.all()
|
||||||
|
serializer_class = common.serializers.NewsFeedEntrySerializer
|
||||||
|
permission_classes = [IsAdminUser, ]
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntryList(NewsFeedMixin, BulkDeleteMixin, ListAPI):
|
||||||
|
"""List view for all news items."""
|
||||||
|
filter_backends = [
|
||||||
|
DjangoFilterBackend,
|
||||||
|
filters.OrderingFilter,
|
||||||
|
]
|
||||||
|
|
||||||
|
ordering_fields = [
|
||||||
|
'published',
|
||||||
|
'author',
|
||||||
|
'read',
|
||||||
|
]
|
||||||
|
|
||||||
|
filterset_fields = [
|
||||||
|
'read',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntryDetail(NewsFeedMixin, RetrieveUpdateDestroyAPI):
|
||||||
|
"""Detail view for an individual news feed object."""
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntryRead(NewsFeedMixin, NotificationReadEdit):
|
||||||
|
"""API endpoint to mark a news item as read."""
|
||||||
|
target = True
|
||||||
|
|
||||||
|
|
||||||
settings_api_urls = [
|
settings_api_urls = [
|
||||||
# User settings
|
# User settings
|
||||||
re_path(r'^user/', include([
|
re_path(r'^user/', include([
|
||||||
@ -428,4 +443,13 @@ common_api_urls = [
|
|||||||
re_path(r'^.*$', NotificationList.as_view(), name='api-notifications-list'),
|
re_path(r'^.*$', NotificationList.as_view(), name='api-notifications-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
# News
|
||||||
|
re_path(r'^news/', include([
|
||||||
|
re_path(r'^(?P<pk>\d+)/', include([
|
||||||
|
re_path(r'^read/', NewsFeedEntryRead.as_view(), name='api-news-read'),
|
||||||
|
re_path(r'.*$', NewsFeedEntryDetail.as_view(), name='api-news-detail'),
|
||||||
|
])),
|
||||||
|
re_path(r'^.*$', NewsFeedEntryList.as_view(), name='api-news-list'),
|
||||||
|
])),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
26
InvenTree/common/migrations/0015_newsfeedentry.py
Normal file
26
InvenTree/common/migrations/0015_newsfeedentry.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 3.2.14 on 2022-07-31 19:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('common', '0014_notificationmessage'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NewsFeedEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('feed_id', models.CharField(max_length=250, unique=True, verbose_name='Id')),
|
||||||
|
('title', models.CharField(max_length=250, verbose_name='Title')),
|
||||||
|
('link', models.URLField(max_length=250, verbose_name='Link')),
|
||||||
|
('published', models.DateTimeField(max_length=250, verbose_name='Published')),
|
||||||
|
('author', models.CharField(max_length=250, verbose_name='Author')),
|
||||||
|
('summary', models.CharField(max_length=250, verbose_name='Summary')),
|
||||||
|
('read', models.BooleanField(default=False, help_text='Was this news item read?', verbose_name='Read')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -1560,6 +1560,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'HOMEPAGE_NEWS': {
|
||||||
|
'name': _('Show News'),
|
||||||
|
'description': _('Show news on the homepage'),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
|
||||||
"LABEL_INLINE": {
|
"LABEL_INLINE": {
|
||||||
'name': _('Inline label display'),
|
'name': _('Inline label display'),
|
||||||
'description': _('Display PDF labels in the browser, instead of downloading as a file'),
|
'description': _('Display PDF labels in the browser, instead of downloading as a file'),
|
||||||
@ -2285,3 +2292,54 @@ class NotificationMessage(models.Model):
|
|||||||
def age_human(self):
|
def age_human(self):
|
||||||
"""Humanized age."""
|
"""Humanized age."""
|
||||||
return naturaltime(self.creation)
|
return naturaltime(self.creation)
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntry(models.Model):
|
||||||
|
"""A NewsFeedEntry represents an entry on the RSS/Atom feed that is generated for InvenTree news.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- feed_id: Unique id for the news item
|
||||||
|
- title: Title for the news item
|
||||||
|
- link: Link to the news item
|
||||||
|
- published: Date of publishing of the news item
|
||||||
|
- author: Author of news item
|
||||||
|
- summary: Summary of the news items content
|
||||||
|
- read: Was this iteam already by a superuser?
|
||||||
|
"""
|
||||||
|
|
||||||
|
feed_id = models.CharField(
|
||||||
|
verbose_name=_('Id'),
|
||||||
|
unique=True,
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
title = models.CharField(
|
||||||
|
verbose_name=_('Title'),
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
link = models.URLField(
|
||||||
|
verbose_name=_('Link'),
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
published = models.DateTimeField(
|
||||||
|
verbose_name=_('Published'),
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
author = models.CharField(
|
||||||
|
verbose_name=_('Author'),
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = models.CharField(
|
||||||
|
verbose_name=_('Summary'),
|
||||||
|
max_length=250,
|
||||||
|
)
|
||||||
|
|
||||||
|
read = models.BooleanField(
|
||||||
|
verbose_name=_('Read'),
|
||||||
|
help_text=_('Was this news item read?'),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
@ -5,7 +5,7 @@ from django.urls import reverse
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.models import (InvenTreeSetting, InvenTreeUserSetting,
|
from common.models import (InvenTreeSetting, InvenTreeUserSetting,
|
||||||
NotificationMessage)
|
NewsFeedEntry, NotificationMessage)
|
||||||
from InvenTree.helpers import construct_absolute_url, get_objectreference
|
from InvenTree.helpers import construct_absolute_url, get_objectreference
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
|
|
||||||
@ -211,3 +211,24 @@ class NotificationReadSerializer(NotificationMessageSerializer):
|
|||||||
self.instance = self.context['instance'] # set instance that should be returned
|
self.instance = self.context['instance'] # set instance that should be returned
|
||||||
self._validated_data = True
|
self._validated_data = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class NewsFeedEntrySerializer(InvenTreeModelSerializer):
|
||||||
|
"""Serializer for the NewsFeedEntry model."""
|
||||||
|
|
||||||
|
read = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta options for NewsFeedEntrySerializer."""
|
||||||
|
|
||||||
|
model = NewsFeedEntry
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'feed_id',
|
||||||
|
'title',
|
||||||
|
'link',
|
||||||
|
'published',
|
||||||
|
'author',
|
||||||
|
'summary',
|
||||||
|
'read',
|
||||||
|
]
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
|
|
||||||
|
import feedparser
|
||||||
|
|
||||||
from InvenTree.tasks import ScheduledTask, scheduled_task
|
from InvenTree.tasks import ScheduledTask, scheduled_task
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -26,3 +29,41 @@ def delete_old_notifications():
|
|||||||
|
|
||||||
# Delete notification records before the specified date
|
# Delete notification records before the specified date
|
||||||
NotificationEntry.objects.filter(updated__lte=before).delete()
|
NotificationEntry.objects.filter(updated__lte=before).delete()
|
||||||
|
|
||||||
|
|
||||||
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
|
def update_news_feed():
|
||||||
|
"""Update the newsfeed."""
|
||||||
|
try:
|
||||||
|
from common.models import NewsFeedEntry
|
||||||
|
except AppRegistryNotReady: # pragma: no cover
|
||||||
|
logger.info("Could not perform 'update_news_feed' - App registry not ready")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fetch and parse feed
|
||||||
|
try:
|
||||||
|
d = feedparser.parse(settings.INVENTREE_NEWS_URL)
|
||||||
|
except Exception as entry: # pragma: no cover
|
||||||
|
logger.warning("update_news_feed: Error parsing the newsfeed", entry)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get a reference list
|
||||||
|
id_list = [a.feed_id for a in NewsFeedEntry.objects.all()]
|
||||||
|
|
||||||
|
# Iterate over entries
|
||||||
|
for entry in d.entries:
|
||||||
|
# Check if id already exsists
|
||||||
|
if entry.id in id_list:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create entry
|
||||||
|
NewsFeedEntry.objects.create(
|
||||||
|
feed_id=entry.id,
|
||||||
|
title=entry.title,
|
||||||
|
link=entry.link,
|
||||||
|
published=entry.published,
|
||||||
|
author=entry.author,
|
||||||
|
summary=entry.summary,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info('update_news_feed: Sync done')
|
||||||
|
@ -306,6 +306,17 @@ loadSalesOrderTable("#table-so-overdue", {
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% settings_value 'HOMEPAGE_NEWS' user=request.user as setting_news %}
|
||||||
|
{% if setting_news and user.is_staff %}
|
||||||
|
|
||||||
|
addHeaderTitle('{% trans "InvenTree News" %}');
|
||||||
|
|
||||||
|
addHeaderAction('news', '{% trans "Current News" %}', 'fa-newspaper');
|
||||||
|
loadNewsFeedTable("#table-news", {
|
||||||
|
url: "{% url 'api-news-list' %}",
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
enableSidebar(
|
enableSidebar(
|
||||||
'index',
|
'index',
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
<tr><td colspan='5'></td></tr>
|
<tr><td colspan='5'></td></tr>
|
||||||
{% include "InvenTree/settings/setting.html" with key="HOMEPAGE_SO_OUTSTANDING" user_setting=True %}
|
{% include "InvenTree/settings/setting.html" with key="HOMEPAGE_SO_OUTSTANDING" user_setting=True %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="HOMEPAGE_SO_OVERDUE" user_setting=True %}
|
{% include "InvenTree/settings/setting.html" with key="HOMEPAGE_SO_OVERDUE" user_setting=True %}
|
||||||
|
<tr><td colspan='5'></td></tr>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="HOMEPAGE_NEWS" user_setting=True %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -167,6 +167,7 @@
|
|||||||
<script defer type='text/javascript' src="{% i18n_static 'search.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'search.js' %}"></script>
|
||||||
<script defer type='text/javascript' src="{% i18n_static 'stock.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'stock.js' %}"></script>
|
||||||
<script defer type='text/javascript' src="{% i18n_static 'plugin.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'plugin.js' %}"></script>
|
||||||
|
<script defer type='text/javascript' src="{% i18n_static 'news.js' %}"></script>
|
||||||
<script defer type='text/javascript' src="{% i18n_static 'tables.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'tables.js' %}"></script>
|
||||||
<script defer type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script>
|
||||||
<script defer type='text/javascript' src="{% i18n_static 'notification.js' %}"></script>
|
<script defer type='text/javascript' src="{% i18n_static 'notification.js' %}"></script>
|
||||||
|
58
InvenTree/templates/js/translated/news.js
Normal file
58
InvenTree/templates/js/translated/news.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load inventree_extras %}
|
||||||
|
|
||||||
|
/* exported
|
||||||
|
loadNewsFeedTable,
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load notification table
|
||||||
|
*/
|
||||||
|
function loadNewsFeedTable(table, options={}, enableDelete=false) {
|
||||||
|
setupFilterList('news', table);
|
||||||
|
|
||||||
|
$(table).inventreeTable({
|
||||||
|
url: options.url,
|
||||||
|
name: 'news',
|
||||||
|
groupBy: false,
|
||||||
|
queryParams: {
|
||||||
|
ordering: 'published',
|
||||||
|
},
|
||||||
|
paginationVAlign: 'bottom',
|
||||||
|
formatNoMatches: function() {
|
||||||
|
return '{% trans "No news found" %}';
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'pk',
|
||||||
|
title: '{% trans "ID" %}',
|
||||||
|
visible: false,
|
||||||
|
switchable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'title',
|
||||||
|
title: '{% trans "Title" %}',
|
||||||
|
sortable: 'true',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
return `<a href="` + row.link + `">` + value + `</a>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'summary',
|
||||||
|
title: '{% trans "Summary" %}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'author',
|
||||||
|
title: '{% trans "Author" %}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'published',
|
||||||
|
title: '{% trans "Published" %}',
|
||||||
|
sortable: 'true',
|
||||||
|
formatter: function(value) {
|
||||||
|
return renderDate(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
@ -76,6 +76,7 @@ class RuleSet(models.Model):
|
|||||||
'plugin_pluginconfig',
|
'plugin_pluginconfig',
|
||||||
'plugin_pluginsetting',
|
'plugin_pluginsetting',
|
||||||
'plugin_notificationusersetting',
|
'plugin_notificationusersetting',
|
||||||
|
'common_newsfeedentry',
|
||||||
],
|
],
|
||||||
'part_category': [
|
'part_category': [
|
||||||
'part_partcategory',
|
'part_partcategory',
|
||||||
|
@ -25,6 +25,7 @@ django-user-sessions # user sessions in DB
|
|||||||
django-weasyprint # django weasyprint integration
|
django-weasyprint # django weasyprint integration
|
||||||
djangorestframework # DRF framework
|
djangorestframework # DRF framework
|
||||||
django-xforwardedfor-middleware # IP forwarding metadata
|
django-xforwardedfor-middleware # IP forwarding metadata
|
||||||
|
feedparser # RSS newsfeed parser
|
||||||
gunicorn # Gunicorn web server
|
gunicorn # Gunicorn web server
|
||||||
pdf2image # PDF to image conversion
|
pdf2image # PDF to image conversion
|
||||||
pillow # Image manipulation
|
pillow # Image manipulation
|
||||||
|
@ -124,6 +124,8 @@ djangorestframework==3.14.0
|
|||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
et-xmlfile==1.1.0
|
et-xmlfile==1.1.0
|
||||||
# via openpyxl
|
# via openpyxl
|
||||||
|
feedparser==6.0.10
|
||||||
|
# via -r requirements.in
|
||||||
fonttools[woff]==4.37.4
|
fonttools[woff]==4.37.4
|
||||||
# via weasyprint
|
# via weasyprint
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
@ -209,6 +211,8 @@ requests-oauthlib==1.3.1
|
|||||||
# via django-allauth
|
# via django-allauth
|
||||||
sentry-sdk==1.9.10
|
sentry-sdk==1.9.10
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
sgmllib3k==1.0.0
|
||||||
|
# via feedparser
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
# via
|
# via
|
||||||
# bleach
|
# bleach
|
||||||
|
Loading…
Reference in New Issue
Block a user