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:
2010-04-30 19:11:03 +01:00
parent 18fa620aab
commit adb172bf47
12 changed files with 220 additions and 31 deletions

View File

@@ -1,12 +1,15 @@
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from hr.models import Application, Recommendation
from hr.models import Application, Recommendation, Audit
class ApplicationAdmin(admin.ModelAdmin):
list_display = ('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)
class RecommendationAdmin(admin.ModelAdmin):
@@ -15,3 +18,8 @@ class RecommendationAdmin(admin.ModelAdmin):
admin.site.register(Recommendation, RecommendationAdmin)
class AuditAdmin(admin.ModelAdmin):
list_display = ('application', 'event', 'date')
admin.site.register(Audit, AuditAdmin)

View File

@@ -15,7 +15,42 @@ APPLICATION_STATUS_CHOICES = (
(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_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',
}

View File

@@ -2,7 +2,7 @@ from django import forms
import settings
from hr.app_defines import *
from hr.models import Application
from hr.models import Application, Audit
from eve_api.models import EVEPlayerCharacter, EVEPlayerCorporation
def CreateRecommendationForm(user):
@@ -40,7 +40,7 @@ def CreateApplicationForm(user):
def CreateApplicationStatusForm(admin):
if admin:
form_choices = APPLICATION_STATUS_CHOICES
form_choices = APPLICATION_STATUS_CHOICES_ADMIN
else:
form_choices = APPLICATION_STATUS_CHOICES_USER
@@ -54,3 +54,10 @@ def CreateApplicationStatusForm(admin):
exclude = ('application')
return ApplicationStatusForm
class NoteForm(forms.ModelForm):
class Meta:
model = Audit
exclude = ('application', 'user', 'event')

View File

@@ -19,9 +19,27 @@ class Application(models.Model):
@property
def status_description(self):
for choice in APPLICATION_STATUS_CHOICES:
if choice[0] == self.status:
if choice[0] == int(self.status):
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):
return self.character.name
@@ -37,4 +55,18 @@ class Recommendation(models.Model):
return self.user_character.name
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]

View File

@@ -9,6 +9,9 @@ urlpatterns = patterns('',
(r'^applications/$', views.view_applications),
(r'^applications/(?P<applicationid>\d+)/$', views.view_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/recommendation/$', views.add_recommendation),

View File

@@ -13,11 +13,30 @@ import settings
from eve_api.models import EVEAccount, EVEPlayerCorporation
from reddit.models import RedditAccount
from hr.forms import CreateRecommendationForm, CreateApplicationForm, CreateApplicationStatusForm
from hr.models import Recommendation, Application
from hr.forms import CreateRecommendationForm, CreateApplicationForm, CreateApplicationStatusForm, NoteForm
from hr.models import Recommendation, Application, Audit
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):
if request.user.is_staff or Group.objects.get(name=settings.HR_STAFF_GROUP) in request.user.groups.all():
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():
hrstaff = True
audit = app.audit_set.all()
else:
hrstaff = False
audit = app.audit_set.filter(event__in=[AUDIT_EVENT_STATUSCHANGE, AUDIT_EVENT_REJECTION_REASON])
if hrstaff or app.status < 1:
appform = CreateApplicationStatusForm(hrstaff)
form = appform(initial={'application': app.id, 'new_status': app.status})
eveacc = EVEAccount.objects.filter(user=app.user)
redditacc = RedditAccount.objects.filter(user=app.user)
recs = Recommendation.objects.filter(application=app)
eveacc = app.user.eveaccount_set.all()
redditacc = app.user.redditaccount_set.all()
recs = app.recommendation_set.all()
posts = []
for acc in redditacc:
@@ -152,28 +173,54 @@ def update_application(request, applicationid):
return HttpResponseRedirect(reverse('hr.views.index'))
if not app.status == form.cleaned_data['new_status']:
app.status = form.cleaned_data['new_status']
app.save()
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)
app.save(user=request.user)
if int(app.status) == APPLICATION_STATUS_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]))
@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))

View 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 %}

View 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 %}

View 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 %}

View File

@@ -13,6 +13,7 @@
<li>Application Status: <b>{{ app.status_description }}</b></li>
</ul>
<h3>Actions</h3>
{% if form %}
<form action="{% url hr.views.update_application app.id %}" method="post">
<table>
@@ -22,6 +23,20 @@
</form>
{% 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 %}
<h3>Recommendations</h3>
<ul>

View File

@@ -2,7 +2,11 @@ Hi {{ app.character }},
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.
{% endif %}
If you have any further questions regarding your application, please contact {{ app.corporation }} via the normal channels.

View File

@@ -92,7 +92,7 @@ setup.</p>
{% for acc in redditaccounts %}
<tr><td>{{ acc.username }}</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>
{% endfor %}
</tbody>