mirror of
https://github.com/nikdoof/test-auth.git
synced 2025-12-14 14:52:15 +00:00
HR milestone 2, application audit log and tracking.
This milestone brings in application auditing, allowing for people to track who modify what applications and provide acception/rejection reasons as part of the messages sent out to the users.
This commit is contained in:
10
hr/admin.py
10
hr/admin.py
@@ -1,12 +1,15 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from hr.models import Application, Recommendation
|
from hr.models import Application, Recommendation, Audit
|
||||||
|
|
||||||
class ApplicationAdmin(admin.ModelAdmin):
|
class ApplicationAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'character', 'status')
|
list_display = ('user', 'character', 'status')
|
||||||
search_fields = ['user', 'character', 'status']
|
search_fields = ['user', 'character', 'status']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
obj.save(user=request.user)
|
||||||
|
|
||||||
admin.site.register(Application, ApplicationAdmin)
|
admin.site.register(Application, ApplicationAdmin)
|
||||||
|
|
||||||
class RecommendationAdmin(admin.ModelAdmin):
|
class RecommendationAdmin(admin.ModelAdmin):
|
||||||
@@ -15,3 +18,8 @@ class RecommendationAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
admin.site.register(Recommendation, RecommendationAdmin)
|
admin.site.register(Recommendation, RecommendationAdmin)
|
||||||
|
|
||||||
|
class AuditAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('application', 'event', 'date')
|
||||||
|
|
||||||
|
admin.site.register(Audit, AuditAdmin)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,42 @@ APPLICATION_STATUS_CHOICES = (
|
|||||||
(APPLICATION_STATUS_COMPLETED, 'Completed'),
|
(APPLICATION_STATUS_COMPLETED, 'Completed'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
APPLICATION_STATUS_CHOICES_ADMIN = (
|
||||||
|
(APPLICATION_STATUS_NOTSUBMITTED, 'Not Submitted'),
|
||||||
|
(APPLICATION_STATUS_AWAITINGREVIEW, 'Submitted'),
|
||||||
|
(APPLICATION_STATUS_QUERY, 'In Query'),
|
||||||
|
(APPLICATION_STATUS_COMPLETED, 'Completed'),
|
||||||
|
)
|
||||||
|
|
||||||
APPLICATION_STATUS_CHOICES_USER = (
|
APPLICATION_STATUS_CHOICES_USER = (
|
||||||
(APPLICATION_STATUS_NOTSUBMITTED, 'Not Submitted'),
|
(APPLICATION_STATUS_NOTSUBMITTED, 'Not Submitted'),
|
||||||
(APPLICATION_STATUS_AWAITINGREVIEW, 'Awaiting Review'),
|
(APPLICATION_STATUS_AWAITINGREVIEW, 'Submitted'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
APPLICATION_STATUS_LOOKUP = {
|
||||||
|
APPLICATION_STATUS_NOTSUBMITTED: 'Not Submitted',
|
||||||
|
APPLICATION_STATUS_AWAITINGREVIEW: 'Submitted',
|
||||||
|
APPLICATION_STATUS_REJECTED: 'Rejected',
|
||||||
|
APPLICATION_STATUS_ACCEPTED: 'Accepted',
|
||||||
|
APPLICATION_STATUS_QUERY: 'In Query',
|
||||||
|
APPLICATION_STATUS_COMPLETED: 'Completed',
|
||||||
|
}
|
||||||
|
|
||||||
|
AUDIT_EVENT_STATUSCHANGE = 0
|
||||||
|
AUDIT_EVENT_NOTE = 1
|
||||||
|
AUDIT_EVENT_REJECTION = 2
|
||||||
|
AUDIT_EVENT_ACCEPTED = 3
|
||||||
|
|
||||||
|
AUDIT_EVENT_CHOICES = (
|
||||||
|
(AUDIT_EVENT_STATUSCHANGE, 'Status Change'),
|
||||||
|
(AUDIT_EVENT_NOTE, 'Note'),
|
||||||
|
(AUDIT_EVENT_REJECTION, 'Rejection Reason'),
|
||||||
|
(AUDIT_EVENT_ACCEPTED, 'Accepted'),
|
||||||
|
)
|
||||||
|
|
||||||
|
AUDIT_EVENT_LOOKUP = {
|
||||||
|
AUDIT_EVENT_STATUSCHANGE: 'Status Change',
|
||||||
|
AUDIT_EVENT_NOTE: 'Note',
|
||||||
|
AUDIT_EVENT_REJECTION: 'Rejection Reason',
|
||||||
|
AUDIT_EVENT_ACCEPTED: 'Accepted',
|
||||||
|
}
|
||||||
|
|||||||
11
hr/forms.py
11
hr/forms.py
@@ -2,7 +2,7 @@ from django import forms
|
|||||||
import settings
|
import settings
|
||||||
|
|
||||||
from hr.app_defines import *
|
from hr.app_defines import *
|
||||||
from hr.models import Application
|
from hr.models import Application, Audit
|
||||||
from eve_api.models import EVEPlayerCharacter, EVEPlayerCorporation
|
from eve_api.models import EVEPlayerCharacter, EVEPlayerCorporation
|
||||||
|
|
||||||
def CreateRecommendationForm(user):
|
def CreateRecommendationForm(user):
|
||||||
@@ -40,7 +40,7 @@ def CreateApplicationForm(user):
|
|||||||
def CreateApplicationStatusForm(admin):
|
def CreateApplicationStatusForm(admin):
|
||||||
|
|
||||||
if admin:
|
if admin:
|
||||||
form_choices = APPLICATION_STATUS_CHOICES
|
form_choices = APPLICATION_STATUS_CHOICES_ADMIN
|
||||||
else:
|
else:
|
||||||
form_choices = APPLICATION_STATUS_CHOICES_USER
|
form_choices = APPLICATION_STATUS_CHOICES_USER
|
||||||
|
|
||||||
@@ -54,3 +54,10 @@ def CreateApplicationStatusForm(admin):
|
|||||||
exclude = ('application')
|
exclude = ('application')
|
||||||
|
|
||||||
return ApplicationStatusForm
|
return ApplicationStatusForm
|
||||||
|
|
||||||
|
|
||||||
|
class NoteForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Audit
|
||||||
|
exclude = ('application', 'user', 'event')
|
||||||
|
|||||||
36
hr/models.py
36
hr/models.py
@@ -19,9 +19,27 @@ class Application(models.Model):
|
|||||||
@property
|
@property
|
||||||
def status_description(self):
|
def status_description(self):
|
||||||
for choice in APPLICATION_STATUS_CHOICES:
|
for choice in APPLICATION_STATUS_CHOICES:
|
||||||
if choice[0] == self.status:
|
if choice[0] == int(self.status):
|
||||||
return choice[1]
|
return choice[1]
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
old_instance = Application.objects.get(id=self.id)
|
||||||
|
if not (old_instance.status == int(self.status)):
|
||||||
|
event = Audit(application=self)
|
||||||
|
if 'user' in kwargs:
|
||||||
|
event.user = kwargs['user']
|
||||||
|
event.event = AUDIT_EVENT_STATUSCHANGE
|
||||||
|
|
||||||
|
event.text = "Status changed from %s to %s" % (old_instance.status_description, self.status_description)
|
||||||
|
event.save()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'user' in kwargs:
|
||||||
|
del kwargs['user']
|
||||||
|
super(Application, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.character.name
|
return self.character.name
|
||||||
|
|
||||||
@@ -37,4 +55,18 @@ class Recommendation(models.Model):
|
|||||||
return self.user_character.name
|
return self.user_character.name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__unicode__()
|
return self.__unicode__()
|
||||||
|
|
||||||
|
class Audit(models.Model):
|
||||||
|
application = models.ForeignKey(Application, blank=False, verbose_name="Application")
|
||||||
|
user = models.ForeignKey(User, blank=True, verbose_name="User")
|
||||||
|
event = models.IntegerField(choices=AUDIT_EVENT_CHOICES,
|
||||||
|
verbose_name="Event Type",
|
||||||
|
help_text="Type of audit event")
|
||||||
|
text = models.TextField(blank=False, verbose_name="Event Text",
|
||||||
|
help_text="Detailed event text")
|
||||||
|
|
||||||
|
date = models.DateTimeField(auto_now_add=True, verbose_name="Event Date")
|
||||||
|
|
||||||
|
def event_description(self):
|
||||||
|
return AUDIT_EVENT_LOOKUP[self.event]
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ urlpatterns = patterns('',
|
|||||||
(r'^applications/$', views.view_applications),
|
(r'^applications/$', views.view_applications),
|
||||||
(r'^applications/(?P<applicationid>\d+)/$', views.view_application),
|
(r'^applications/(?P<applicationid>\d+)/$', views.view_application),
|
||||||
(r'^applications/(?P<applicationid>\d+)/update/$', views.update_application),
|
(r'^applications/(?P<applicationid>\d+)/update/$', views.update_application),
|
||||||
|
(r'^applications/(?P<applicationid>\d+)/note/$', views.add_note),
|
||||||
|
(r'^applications/(?P<applicationid>\d+)/reject/$', views.reject_application),
|
||||||
|
(r'^applications/(?P<applicationid>\d+)/accept/$', views.accept_application),
|
||||||
|
|
||||||
(r'^add/application/$', views.add_application),
|
(r'^add/application/$', views.add_application),
|
||||||
(r'^add/recommendation/$', views.add_recommendation),
|
(r'^add/recommendation/$', views.add_recommendation),
|
||||||
|
|||||||
95
hr/views.py
95
hr/views.py
@@ -13,11 +13,30 @@ import settings
|
|||||||
from eve_api.models import EVEAccount, EVEPlayerCorporation
|
from eve_api.models import EVEAccount, EVEPlayerCorporation
|
||||||
from reddit.models import RedditAccount
|
from reddit.models import RedditAccount
|
||||||
|
|
||||||
from hr.forms import CreateRecommendationForm, CreateApplicationForm, CreateApplicationStatusForm
|
from hr.forms import CreateRecommendationForm, CreateApplicationForm, CreateApplicationStatusForm, NoteForm
|
||||||
from hr.models import Recommendation, Application
|
from hr.models import Recommendation, Application, Audit
|
||||||
|
|
||||||
from app_defines import *
|
from app_defines import *
|
||||||
|
|
||||||
|
### Shared Functions
|
||||||
|
|
||||||
|
def send_message(application, message_type, note=None):
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
subject = render_to_string('hr/emails/%s_subject.txt' % message_type, { 'app': application })
|
||||||
|
subject = ''.join(subject.splitlines())
|
||||||
|
message = render_to_string('hr/emails/%s.txt' % message_type, { 'app': application, 'note': note })
|
||||||
|
try:
|
||||||
|
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [application.user.email])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(application.user.redditaccount_set.all()) > 0:
|
||||||
|
from reddit.api import Inbox
|
||||||
|
ib = Inbox(settings.REDDIT_USER, settings.REDDIT_PASSWD)
|
||||||
|
ib.send(application.user.redditaccount_set.all()[0].username, subject, message)
|
||||||
|
|
||||||
|
### General Views
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
if request.user.is_staff or Group.objects.get(name=settings.HR_STAFF_GROUP) in request.user.groups.all():
|
if request.user.is_staff or Group.objects.get(name=settings.HR_STAFF_GROUP) in request.user.groups.all():
|
||||||
hrstaff = True
|
hrstaff = True
|
||||||
@@ -43,16 +62,18 @@ def view_application(request, applicationid):
|
|||||||
|
|
||||||
if request.user.is_staff or Group.objects.get(name=settings.HR_STAFF_GROUP) in request.user.groups.all():
|
if request.user.is_staff or Group.objects.get(name=settings.HR_STAFF_GROUP) in request.user.groups.all():
|
||||||
hrstaff = True
|
hrstaff = True
|
||||||
|
audit = app.audit_set.all()
|
||||||
else:
|
else:
|
||||||
hrstaff = False
|
hrstaff = False
|
||||||
|
audit = app.audit_set.filter(event__in=[AUDIT_EVENT_STATUSCHANGE, AUDIT_EVENT_REJECTION_REASON])
|
||||||
|
|
||||||
if hrstaff or app.status < 1:
|
if hrstaff or app.status < 1:
|
||||||
appform = CreateApplicationStatusForm(hrstaff)
|
appform = CreateApplicationStatusForm(hrstaff)
|
||||||
form = appform(initial={'application': app.id, 'new_status': app.status})
|
form = appform(initial={'application': app.id, 'new_status': app.status})
|
||||||
|
|
||||||
eveacc = EVEAccount.objects.filter(user=app.user)
|
eveacc = app.user.eveaccount_set.all()
|
||||||
redditacc = RedditAccount.objects.filter(user=app.user)
|
redditacc = app.user.redditaccount_set.all()
|
||||||
recs = Recommendation.objects.filter(application=app)
|
recs = app.recommendation_set.all()
|
||||||
|
|
||||||
posts = []
|
posts = []
|
||||||
for acc in redditacc:
|
for acc in redditacc:
|
||||||
@@ -152,28 +173,54 @@ def update_application(request, applicationid):
|
|||||||
return HttpResponseRedirect(reverse('hr.views.index'))
|
return HttpResponseRedirect(reverse('hr.views.index'))
|
||||||
|
|
||||||
if not app.status == form.cleaned_data['new_status']:
|
if not app.status == form.cleaned_data['new_status']:
|
||||||
|
|
||||||
app.status = form.cleaned_data['new_status']
|
app.status = form.cleaned_data['new_status']
|
||||||
app.save()
|
app.save(user=request.user)
|
||||||
|
|
||||||
def send_message(application, message_type):
|
|
||||||
from django.core.mail import send_mail
|
|
||||||
subject = render_to_string('hr/emails/%s_subject.txt' % message_type, { 'app': app })
|
|
||||||
subject = ''.join(subject.splitlines())
|
|
||||||
message = render_to_string('hr/emails/%s.txt' % message_type, { 'app': app })
|
|
||||||
try:
|
|
||||||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [application.user.email])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if len(application.user.redditaccount_set.all()) > 0:
|
|
||||||
from reddit.api import Inbox
|
|
||||||
ib = Inbox(settings.REDDIT_USER, settings.REDDIT_PASSWD)
|
|
||||||
ib.send(application.user.redditaccount_set.all()[0].username, subject, message)
|
|
||||||
|
|
||||||
if int(app.status) == APPLICATION_STATUS_ACCEPTED:
|
if int(app.status) == APPLICATION_STATUS_ACCEPTED:
|
||||||
send_message(app, 'accepted')
|
send_message(app, 'accepted')
|
||||||
elif int(app.status) == APPLICATION_STATUS_REJECTED:
|
|
||||||
send_message(app, 'rejected')
|
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('hr.views.view_application', args=[applicationid]))
|
return HttpResponseRedirect(reverse('hr.views.view_application', args=[applicationid]))
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_note(request, applicationid):
|
||||||
|
if request.method == 'POST':
|
||||||
|
obj = Audit(application=Application.objects.get(id=applicationid), user=request.user, event=AUDIT_EVENT_NOTE)
|
||||||
|
form = NoteForm(request.POST, instance=obj)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return HttpResponseRedirect(reverse('hr.views.view_application', args=[applicationid]))
|
||||||
|
|
||||||
|
form = NoteForm()
|
||||||
|
return render_to_response('hr/applications/add_note.html', locals(), context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def reject_application(request, applicationid):
|
||||||
|
if request.method == 'POST':
|
||||||
|
obj = Audit(application=Application.objects.get(id=applicationid), user=request.user, event=AUDIT_EVENT_REJECTION)
|
||||||
|
form = NoteForm(request.POST, instance=obj)
|
||||||
|
if form.is_valid():
|
||||||
|
obj = form.save()
|
||||||
|
obj.application.status = APPLICATION_STATUS_REJECTED
|
||||||
|
obj.application.save(user=request.user)
|
||||||
|
send_message(obj.application, 'rejected', note=obj.text)
|
||||||
|
return HttpResponseRedirect(reverse('hr.views.view_application', args=[applicationid]))
|
||||||
|
|
||||||
|
form = NoteForm()
|
||||||
|
return render_to_response('hr/applications/reject.html', locals(), context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def accept_application(request, applicationid):
|
||||||
|
if request.method == 'POST':
|
||||||
|
obj = Audit(application=Application.objects.get(id=applicationid), user=request.user, event=AUDIT_EVENT_ACCEPTED)
|
||||||
|
form = NoteForm(request.POST, instance=obj)
|
||||||
|
if form.is_valid():
|
||||||
|
obj = form.save()
|
||||||
|
obj.application.status = APPLICATION_STATUS_ACCEPTED
|
||||||
|
obj.application.save(user=request.user)
|
||||||
|
send_message(obj.application, 'accepted', note=obj.text)
|
||||||
|
return HttpResponseRedirect(reverse('hr.views.view_application', args=[applicationid]))
|
||||||
|
|
||||||
|
form = NoteForm()
|
||||||
|
return render_to_response('hr/applications/accept.html', locals(), context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|||||||
13
templates/hr/applications/accept.html
Normal file
13
templates/hr/applications/accept.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Accept Application{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Fill in a note you want to send to the user.</p>
|
||||||
|
<form action="{% url hr.views.accept_application applicationid %}" method="post">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Apply" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
12
templates/hr/applications/add_note.html
Normal file
12
templates/hr/applications/add_note.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Add Note to Application{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="{% url hr.views.add_note applicationid %}" method="post">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Apply" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
13
templates/hr/applications/reject.html
Normal file
13
templates/hr/applications/reject.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Reject Application{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Fill in the rejection reason below, please note, this will be sent out to the user.</p>
|
||||||
|
<form action="{% url hr.views.reject_application applicationid %}" method="post">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Apply" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<li>Application Status: <b>{{ app.status_description }}</b></li>
|
<li>Application Status: <b>{{ app.status_description }}</b></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h3>Actions</h3>
|
||||||
{% if form %}
|
{% if form %}
|
||||||
<form action="{% url hr.views.update_application app.id %}" method="post">
|
<form action="{% url hr.views.update_application app.id %}" method="post">
|
||||||
<table>
|
<table>
|
||||||
@@ -22,6 +23,20 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if hrstaff %}
|
||||||
|
<p><a href="{% url hr.views.add_note app.id %}">Add Note</a>, <a href="{% url hr.views.reject_application app.id %}">Reject Application</a>, <a href="{% url hr.views.accept_application app.id %}">Accept Application</a></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if audit %}
|
||||||
|
<h3>Event Log</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Event Type</th><th>Changed By</th><th>Changed Date</th><th>Event Details</th></tr>
|
||||||
|
{% for a in audit %}
|
||||||
|
<tr><td>{{ a.event_description }}</td><td>{{ a.user }}</td><td>{{ a.date }}</td><td>{{ a.text }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if recs %}
|
{% if recs %}
|
||||||
<h3>Recommendations</h3>
|
<h3>Recommendations</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ Hi {{ app.character }},
|
|||||||
|
|
||||||
Your application to {{ app.corporation }} has been rejected.
|
Your application to {{ app.corporation }} has been rejected.
|
||||||
|
|
||||||
|
{% if note %}
|
||||||
|
{{ note }}
|
||||||
|
{% else %}
|
||||||
One of our Personnel people will contact you in a seperate method to explain why you have been rejected.
|
One of our Personnel people will contact you in a seperate method to explain why you have been rejected.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
If you have any further questions regarding your application, please contact {{ app.corporation }} via the normal channels.
|
If you have any further questions regarding your application, please contact {{ app.corporation }} via the normal channels.
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ setup.</p>
|
|||||||
{% for acc in redditaccounts %}
|
{% for acc in redditaccounts %}
|
||||||
<tr><td>{{ acc.username }}</td>
|
<tr><td>{{ acc.username }}</td>
|
||||||
<td>{{ acc.date_created }}</td>
|
<td>{{ acc.date_created }}</td>
|
||||||
<td>{% if acc.validated %}Yes{% else %}No (<a href="http://www.reddit.com/message/compose/?to=DredditVerification&subject=Validation%3a%20{{user.username}}">Validate</a>){% endif %}</td>
|
<td>{% if acc.validated %}Yes{% else %}No (<a href="http://www.reddit.com/message/compose/?to=DredditVerification&subject=Validation%3a%20{{user.username}}" target="_blank">Validate</a>){% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user