mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-31 04:12:15 +00:00
Compare commits
10 Commits
assets_emb
...
assets_mis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5696cf73ce | ||
|
|
096ca24336 | ||
| c779c24d63 | |||
| dad9797f74 | |||
| 69facd5dc6 | |||
| 406a9dad36 | |||
| 81c32baa8e | |||
| eef0682e4f | |||
| 2465390889 | |||
| fc9ce9f01c |
@@ -6,34 +6,6 @@ from django.urls import reverse
|
|||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
def get_oembed(login_url, request, oembed_view, kwargs):
|
|
||||||
context = {}
|
|
||||||
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
|
|
||||||
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
|
|
||||||
resp = render(request, 'login_redirect.html', context=context)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
|
|
||||||
def has_oembed(oembed_view, login_url=None):
|
|
||||||
if not login_url:
|
|
||||||
from django.conf import settings
|
|
||||||
login_url = settings.LOGIN_URL
|
|
||||||
|
|
||||||
def _dec(view_func):
|
|
||||||
def _checklogin(request, *args, **kwargs):
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
return view_func(request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
if oembed_view is not None:
|
|
||||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
|
||||||
else:
|
|
||||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
|
||||||
_checklogin.__doc__ = view_func.__doc__
|
|
||||||
_checklogin.__dict__ = view_func.__dict__
|
|
||||||
return _checklogin
|
|
||||||
return _dec
|
|
||||||
|
|
||||||
|
|
||||||
def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
||||||
"""
|
"""
|
||||||
Decorator for views that checks that the user passes the given test.
|
Decorator for views that checks that the user passes the given test.
|
||||||
@@ -53,7 +25,11 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
|||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
elif not request.user.is_authenticated:
|
elif not request.user.is_authenticated:
|
||||||
if oembed_view is not None:
|
if oembed_view is not None:
|
||||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
context = {}
|
||||||
|
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
|
||||||
|
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
|
||||||
|
resp = render(request, 'login_redirect.html', context=context)
|
||||||
|
return resp
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{% extends 'base_rigs.html' %}
|
|
||||||
|
|
||||||
{% block title %}Password Reset Disabled{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Password reset is disabled</h1>
|
|
||||||
<p> We are very sorry for the inconvenience, but due to a security vulnerability, password reset is currently disabled until the vulnerability can be patched.</p>
|
|
||||||
<p> If you are locked out of your account, please contact an administrator and we can manually perform a reset</p>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -19,7 +19,7 @@ urlpatterns = [
|
|||||||
url('^user/login/$', views.login, name='login'),
|
url('^user/login/$', views.login, name='login'),
|
||||||
url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'),
|
url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'),
|
||||||
|
|
||||||
url(r'^user/password_reset/$', views.PasswordResetDisabled.as_view()),
|
url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}),
|
||||||
|
|
||||||
# People
|
# People
|
||||||
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
|
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
|
||||||
|
|||||||
@@ -392,7 +392,3 @@ class ResetApiKey(generic.RedirectView):
|
|||||||
self.request.user.save()
|
self.request.user.save()
|
||||||
|
|
||||||
return reverse_lazy('profile_detail')
|
return reverse_lazy('profile_detail')
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetDisabled(generic.TemplateView):
|
|
||||||
template_name = "RIGS/password_reset_disable.html"
|
|
||||||
|
|||||||
21
assets/migrations/0009_auto_20200102_1933.py
Normal file
21
assets/migrations/0009_auto_20200102_1933.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-01-02 19:33
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0008_auto_20191206_2124'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='assetcategory',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Asset Category', 'verbose_name_plural': 'Asset Categories'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='assetstatus',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Asset Status', 'verbose_name_plural': 'Asset Statuses'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
# Generated by Django 2.0.13 on 2020-01-03 22:15
|
# Generated by Django 2.0.13 on 2020-01-02 20:42
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
replaces = [('assets', '0009_auto_20200102_1933'), ('assets', '0010_assetstatus_display_class'), ('assets', '0011_auto_20200102_2040')]
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('assets', '0008_auto_20191206_2124'),
|
('assets', '0008_auto_20191206_2124'),
|
||||||
]
|
]
|
||||||
@@ -22,11 +23,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='assetstatus',
|
model_name='assetstatus',
|
||||||
name='display_class',
|
name='display_class',
|
||||||
field=models.CharField(blank=True, help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80, null=True),
|
field=models.CharField(help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80),
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='asset',
|
|
||||||
name='purchased_from',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Supplier'),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
18
assets/migrations/0010_assetstatus_display_class.py
Normal file
18
assets/migrations/0010_assetstatus_display_class.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-01-02 19:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0009_auto_20200102_1933'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assetstatus',
|
||||||
|
name='display_class',
|
||||||
|
field=models.TextField(default='', help_text='HTML class to be appended to alter display of assets with this status, such as in the list.'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
assets/migrations/0010_auto_20200103_2134.py
Normal file
19
assets/migrations/0010_auto_20200103_2134.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-01-03 21:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0009_auto_20200102_1933_squashed_0011_auto_20200102_2040'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='purchased_from',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Supplier'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
assets/migrations/0011_auto_20200102_2040.py
Normal file
18
assets/migrations/0011_auto_20200102_2040.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-01-02 20:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0010_assetstatus_display_class'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assetstatus',
|
||||||
|
name='display_class',
|
||||||
|
field=models.CharField(help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -33,7 +33,7 @@ class AssetStatus(models.Model):
|
|||||||
name = models.CharField(max_length=80)
|
name = models.CharField(max_length=80)
|
||||||
should_show = models.BooleanField(
|
should_show = models.BooleanField(
|
||||||
default=True, help_text="Should this be shown by default in the asset list.")
|
default=True, help_text="Should this be shown by default in the asset list.")
|
||||||
display_class = models.CharField(max_length=80, blank=True, null=True, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.")
|
display_class = models.CharField(max_length=80, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
{% extends 'base_embed.html' %}
|
|
||||||
{% load static from staticfiles %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<a href="/assets">
|
|
||||||
<span class="source"> TEC Asset Database</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<h3>
|
|
||||||
<a href="{% url 'asset_detail' object.asset_id %}">Asset: {{ object.asset_id }} | {{ object.description }} </a>
|
|
||||||
<small class="label label-default">
|
|
||||||
<strong>Category:</strong>
|
|
||||||
{{ object.category }}
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<small class="label label-{{ object.status.display_class|default:'default' }}">
|
|
||||||
<strong>Status:</strong>
|
|
||||||
{{ object.status }}
|
|
||||||
</small>
|
|
||||||
</h3>
|
|
||||||
<br>
|
|
||||||
{% if object.serial_number %}
|
|
||||||
<p>
|
|
||||||
<strong>Serial Number: </strong>
|
|
||||||
{{ object.serial_number }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if object.comments %}
|
|
||||||
<p>
|
|
||||||
<strong>Comments: </strong>
|
|
||||||
{{ object.comments|linebreaksbr }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if not edit and perms.assets.view_asset %}
|
{% if not edit %}
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'asset_history' object.asset_id %}" title="View Revision History">
|
<a href="{% url 'asset_history' object.asset_id %}" title="View Revision History">
|
||||||
|
|||||||
@@ -3,34 +3,24 @@ from django.urls import path
|
|||||||
from assets import views, models
|
from assets import views, models
|
||||||
from RIGS import versioning
|
from RIGS import versioning
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from PyRIGS.decorators import permission_required_with_403
|
||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
|
||||||
from PyRIGS.decorators import has_oembed, permission_required_with_403
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.AssetList.as_view(), name='asset_index'),
|
path('', views.AssetList.as_view(), name='asset_index'),
|
||||||
path('asset/list/', views.AssetList.as_view(), name='asset_list'),
|
path('asset/list/', views.AssetList.as_view(), name='asset_list'),
|
||||||
# Lazy way to enable the oembed redirect...
|
path('asset/id/<str:pk>/', views.AssetDetail.as_view(), name='asset_detail'),
|
||||||
path('asset/id/<str:pk>/', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'),
|
|
||||||
path('asset/create/', permission_required_with_403('assets.add_asset')
|
path('asset/create/', permission_required_with_403('assets.add_asset')
|
||||||
(views.AssetCreate.as_view()), name='asset_create'),
|
(views.AssetCreate.as_view()), name='asset_create'),
|
||||||
path('asset/id/<str:pk>/edit/', permission_required_with_403('assets.change_asset')
|
path('asset/id/<str:pk>/edit/', permission_required_with_403('assets.change_asset')
|
||||||
(views.AssetEdit.as_view()), name='asset_update'),
|
(views.AssetEdit.as_view()), name='asset_update'),
|
||||||
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')
|
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')
|
||||||
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
|
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
|
||||||
path('asset/id/<str:pk>/history/', permission_required_with_403('assets.view_asset')(views.AssetVersionHistory.as_view()),
|
path('asset/id/<str:pk>/history/', views.AssetVersionHistory.as_view(),
|
||||||
name='asset_history', kwargs={'model': models.Asset}),
|
name='asset_history', kwargs={'model': models.Asset}),
|
||||||
path('activity', permission_required_with_403('assets.view_asset')
|
path('activity', permission_required_with_403('assets.view_asset')
|
||||||
(views.ActivityTable.as_view()), name='asset_activity_table'),
|
(views.ActivityTable.as_view()), name='asset_activity_table'),
|
||||||
|
|
||||||
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
|
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
|
||||||
path('asset/id/<str:pk>/embed/',
|
|
||||||
xframe_options_exempt(
|
|
||||||
login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())),
|
|
||||||
name='asset_embed'),
|
|
||||||
path('asset/id/<str:pk>/oembed_json/',
|
|
||||||
views.AssetOembed.as_view(),
|
|
||||||
name='asset_oembed'),
|
|
||||||
|
|
||||||
path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
|
path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
|
||||||
path('supplier/<int:pk>', views.SupplierDetail.as_view(), name='supplier_detail'),
|
path('supplier/<int:pk>', views.SupplierDetail.as_view(), name='supplier_detail'),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@@ -10,8 +9,6 @@ from django.shortcuts import get_object_or_404
|
|||||||
from assets import models, forms
|
from assets import models, forms
|
||||||
from RIGS import versioning
|
from RIGS import versioning
|
||||||
|
|
||||||
import simplejson
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
class AssetList(LoginRequiredMixin, generic.ListView):
|
class AssetList(LoginRequiredMixin, generic.ListView):
|
||||||
@@ -152,28 +149,6 @@ class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class AssetOembed(generic.View):
|
|
||||||
model = models.Asset
|
|
||||||
|
|
||||||
def get(self, request, pk=None):
|
|
||||||
embed_url = reverse('asset_embed', args=[pk])
|
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
|
||||||
'version': '1.0',
|
|
||||||
'type': 'rich',
|
|
||||||
'height': '250'
|
|
||||||
}
|
|
||||||
|
|
||||||
json = simplejson.JSONEncoderForHTML().encode(data)
|
|
||||||
return HttpResponse(json, content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
class AssetEmbed(AssetDetail):
|
|
||||||
template_name = 'asset_embed.html'
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierList(generic.ListView):
|
class SupplierList(generic.ListView):
|
||||||
model = models.Supplier
|
model = models.Supplier
|
||||||
template_name = 'supplier_list.html'
|
template_name = 'supplier_list.html'
|
||||||
@@ -238,9 +213,8 @@ class SupplierVersionHistory(versioning.VersionHistory):
|
|||||||
template_name = "asset_version_history.html"
|
template_name = "asset_version_history.html"
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Reduce SQL queries
|
||||||
class AssetVersionHistory(versioning.VersionHistory):
|
class AssetVersionHistory(versioning.VersionHistory):
|
||||||
template_name = "asset_version_history.html"
|
|
||||||
|
|
||||||
def get_object(self, **kwargs):
|
def get_object(self, **kwargs):
|
||||||
return get_object_or_404(models.Asset, asset_id=self.kwargs['pk'])
|
return get_object_or_404(models.Asset, asset_id=self.kwargs['pk'])
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block titleelements %}
|
{% block titleelements %}
|
||||||
{# % if perms.assets.view_asset % #}
|
{% if perms.assets.view_asset %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assets<b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assets<b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@@ -20,19 +20,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{# % endif % #}
|
{% endif %}
|
||||||
{# % if perms.assets.view_supplier % #}
|
{% if perms.assets.view_supplier %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Suppliers<b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Suppliers<b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="{% url 'supplier_list' %}"><span class="glyphicon glyphicon-list"></span>
|
<li><a href="{% url 'supplier_list' %}"><span class="glyphicon glyphicon-list"></span>
|
||||||
List Suppliers</a></li>
|
List Suppliers</a></li>
|
||||||
{% if perms.assets.add_supplier %}
|
{% if perms.assets.add_asset %}
|
||||||
<li><a href="{% url 'supplier_create' %}"><span class="glyphicon glyphicon-plus"></span> Create Supplier</a></li>
|
<li><a href="{% url 'supplier_create' %}"><span class="glyphicon glyphicon-plus"></span> Create Supplier</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{# % endif % #}
|
{% endif %}
|
||||||
{% if perms.assets.view_asset %}
|
{% if perms.assets.view_asset %}
|
||||||
<li><a href="{% url 'asset_activity_table' %}">Recent Changes</a></li>
|
<li><a href="{% url 'asset_activity_table' %}">Recent Changes</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user