attachments #2

Merged
Etzelia merged 9 commits from refs/pull/2/head into master 2021-05-06 17:50:18 +00:00
34 changed files with 555 additions and 153 deletions

7
LICENSE 100644
View File

@ -0,0 +1,7 @@
Copyright 2020 Etzelia
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

15
REQUIREMENTS.md 100644
View File

@ -0,0 +1,15 @@
# Requirements
This project was built with (and should work with non-breaking upgrades of):
| Package | Version |
|-------------------------------------------------------|---------|
| [discord.py](https://pypi.org/project/discord.py/) | 1.3.4 |
| [Django](https://pypi.org/project/Django/) | 2.2 |
| [mcstatus](https://pypi.org/project/mcstatus/) | 3.1.1 |
| [mysqlclient*](https://pypi.org/project/mysqlclient/) | 1.4.6 |
| [PyYAML](https://pypi.org/project/PyYAML/) | 5.3.1 |
*or any other preferred DB

View File

@ -0,0 +1 @@
default_app_config = 'minecraft_manager.apps.MinecraftManagerAppConfig'

View File

@ -4,7 +4,7 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert, Attachment
from minecraft_manager.api.admin import register as api_register from minecraft_manager.api.admin import register as api_register
@ -90,6 +90,14 @@ class IPAdmin(admin.ModelAdmin):
search_fields = ["player__username", "ip"] search_fields = ["player__username", "ip"]
class AttachmentAdmin(admin.ModelAdmin):
list_display = ["file_name", "model_name"]
def model_name(self, attachment):
return attachment.ref_name
model_name.short_description = "Model"
try: try:
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
@ -101,6 +109,7 @@ try:
admin.site.register(Player, PlayerAdmin) admin.site.register(Player, PlayerAdmin)
admin.site.register(IP, IPAdmin) admin.site.register(IP, IPAdmin)
admin.site.register(Alert) admin.site.register(Alert)
admin.site.register(Attachment, AttachmentAdmin)
api_register() api_register()
except admin.sites.AlreadyRegistered: except admin.sites.AlreadyRegistered:
pass pass

12
apps.py 100644
View File

@ -0,0 +1,12 @@
from django.apps import AppConfig
from django.db.models.signals import pre_delete
from minecraft_manager.signals.pre_delete import attachment_delete
class MinecraftManagerAppConfig(AppConfig):
name = 'minecraft_manager'
verbose_name = "Minecraft Manager"
def ready(self):
pre_delete.connect(attachment_delete)

View File

@ -153,6 +153,29 @@ class Commands(commands.Cog):
if not api.plugin(api.PLUGIN_DENY, application.username): if not api.plugin(api.PLUGIN_DENY, application.username):
await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?") await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?")
@commands.command()
async def clear(self, ctx, app_id: int):
await self._clear(ctx, app_id)
@app.command("clear")
async def app_clear(self, ctx, app_id: int):
await self._clear(ctx, app_id)
async def _clear(self, ctx, app_id: int):
application = get_application(app_id)
if not application:
await self.bot.discord_message(ctx.message.channel, "An Application with that ID doesn't exist.")
return
if not application.accepted:
application.accepted = None
application.save()
if Player.objects.filter(username__iexact=application.username).exists():
player = Player.objects.get(username__iexact=application.username)
player.application_id = application.id
player.save()
await self.bot.discord_message(ctx.message.channel, "App ID **{0}** was successfully cleared.".format(app_id))
@commands.command() @commands.command()
async def search(self, ctx, search: str): async def search(self, ctx, search: str):
await self._search(ctx, search) await self._search(ctx, search)

2
external/views.py vendored
View File

@ -73,7 +73,7 @@ class Apply(View):
captcha = mcm_utils.Captcha(request.POST) captcha = mcm_utils.Captcha(request.POST)
valid = form.is_valid() valid = form.is_valid()
r = rules() r = rules()
valid_answer = not r["validate"] or r["answer"] == form.data['read_rules'] valid_answer = not r["validate"] or r["answer"].casefold() == form.data['read_rules'].casefold()
if valid and valid_username and valid_answer and captcha.success: if valid and valid_username and valid_answer and captcha.success:
app = form.save() app = form.save()
msg = mcm_utils.build_application(app) msg = mcm_utils.build_application(app)

View File

@ -30,7 +30,7 @@ class TicketForm(ModelForm):
fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z'] fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z']
widgets = { widgets = {
'player': TextInput, 'player': TextInput,
'message': Textarea, 'message': Textarea(attrs={'style': 'display: block;'}),
} }
@ -39,7 +39,7 @@ class NoteForm(ModelForm):
model = Note model = Note
fields = ['player', 'message', 'importance'] fields = ['player', 'message', 'importance']
widgets = { widgets = {
'message': Textarea 'message': Textarea(attrs={'style': 'display: block;'})
} }

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.3 on 2021-02-06 21:40
from django.db import migrations, models
import minecraft_manager.models
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0014_auto_20190930_1103'),
]
operations = [
migrations.CreateModel(
name='Attachment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ref_model', models.CharField(choices=[('T', 'Ticket Note'), ('N', 'Note')], max_length=1)),
('ref_id', models.IntegerField()),
('file', models.FileField(upload_to=minecraft_manager.models._attachment_upload_to)),
('created', models.DateTimeField(auto_now_add=True)),
],
),
migrations.AlterField(
model_name='application',
name='reference',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='How did you find out about our server?'),
),
]

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from os.path import basename
import logging, yaml, pytz, json, os import logging, yaml, pytz, json, os
from django.conf import settings from django.conf import settings
from datetime import datetime from datetime import datetime
@ -39,7 +40,7 @@ class UserSettings(models.Model):
auth_user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True) auth_user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
default_results = models.SmallIntegerField("Default Results", default=10, choices=RESULT_OPTIONS) default_results = models.SmallIntegerField("Default Results", default=10, choices=RESULT_OPTIONS)
default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS) default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS)
default_timezone = models.CharField("Timezone", max_length=20, default='UTC', choices=TIMEZONES) default_timezone = models.CharField("Timezone", max_length=50, default='UTC', choices=TIMEZONES)
search_player_ip = models.BooleanField("Include IP in Player search", default=False) search_player_ip = models.BooleanField("Include IP in Player search", default=False)
show_timestamp_chat = models.BooleanField("Show Timestamp By Chat", default=False) show_timestamp_chat = models.BooleanField("Show Timestamp By Chat", default=False)
last_ip = models.CharField(max_length=30, default="127.0.0.1", editable=False) last_ip = models.CharField(max_length=30, default="127.0.0.1", editable=False)
@ -61,7 +62,7 @@ class Application(models.Model):
player_type = models.TextField("What type of player are you?", max_length=300) player_type = models.TextField("What type of player are you?", max_length=300)
ever_banned = models.BooleanField("Have you ever been banned?", default=False) ever_banned = models.BooleanField("Have you ever been banned?", default=False)
ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True) ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True)
reference = models.CharField("Were you referred to our server?", max_length=50, blank=True, null=True) reference = models.CharField("How did you find out about our server?", max_length=50, blank=True, null=True)
read_rules = models.CharField("Have you read the rules?", max_length=10) read_rules = models.CharField("Have you read the rules?", max_length=10)
accepted = models.NullBooleanField() accepted = models.NullBooleanField()
date = models.DateTimeField(auto_now_add=True, blank=True, null=True) date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
@ -252,6 +253,10 @@ class Ticket(models.Model):
def date_display(self): def date_display(self):
return str(self.date).split(".")[0] return str(self.date).split(".")[0]
@property
def notes(self):
return TicketNote.objects.filter(ticket=self)
def __str__(self): def __str__(self):
return "{}: {}".format(self.issuer, self.snippet) return "{}: {}".format(self.issuer, self.snippet)
@ -263,6 +268,10 @@ class TicketNote(models.Model):
last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True) last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True)
date = models.DateTimeField(auto_now_add=True, blank=True, null=True) date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
@property
def attachments(self):
return Attachment.objects.filter(ref_model=RefModels.TICKET_NOTE[0], ref_id=self.id)
class Meta: class Meta:
verbose_name = "Ticket Note" verbose_name = "Ticket Note"
verbose_name_plural = "Ticket Notes" verbose_name_plural = "Ticket Notes"
@ -324,10 +333,49 @@ class Note(models.Model):
def date_display(self): def date_display(self):
return str(self.date).split(".")[0] return str(self.date).split(".")[0]
@property
def attachments(self):
return Attachment.objects.filter(ref_model=RefModels.NOTE[0], ref_id=self.id)
def __str__(self): def __str__(self):
return "{}: {}".format(self.issuee, self.snippet) return "{}: {}".format(self.issuee, self.snippet)
class RefModels:
TICKET_NOTE = 'T', 'Ticket Note'
NOTE = 'N', 'Note'
choices = TICKET_NOTE, NOTE
@staticmethod
def label(ref: str):
for c in RefModels.choices:
if c[0] == ref:
return c[1]
return "None"
def _attachment_upload_to(instance, filename):
return f"attachments/{instance.ref_name.lower().replace(' ', '_')}s/{instance.ref_id}/{filename}"
class Attachment(models.Model):
ref_model = models.CharField(max_length=1, choices=RefModels.choices)
ref_id = models.IntegerField()
file = models.FileField(upload_to=_attachment_upload_to)
created = models.DateTimeField(auto_now_add=True)
@property
def ref_name(self):
return RefModels.label(self.ref_model)
@property
def file_name(self):
return basename(self.file.name)
def __str__(self):
return self.file.name
class IPManager(models.Manager): class IPManager(models.Manager):
def get_queryset(self): def get_queryset(self):
users = User.objects.filter(is_active=True) users = User.objects.filter(is_active=True)

View File

View File

@ -0,0 +1,2 @@
def attachment_delete(sender, instance, **kwargs):
instance.file.delete(False)

View File

@ -1,4 +1,9 @@
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic"); @import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
/* Custom CSS */
.nav-sidebar {
border-right: 1px solid #464545;
}
/*! /*!
* bootswatch v3.3.7 * bootswatch v3.3.7
* Homepage: http://bootswatch.com * Homepage: http://bootswatch.com

View File

@ -1,4 +1,9 @@
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic"); @import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
/* Custom CSS */
.nav-sidebar {
border-right: 1px solid #ecf0f1;
}
/*! /*!
* bootswatch v3.3.7 * bootswatch v3.3.7
* Homepage: http://bootswatch.com * Homepage: http://bootswatch.com

View File

@ -1,3 +1,9 @@
@import url("https://fonts.googleapis.com/css?family=Roboto:400,700");
/* Custom CSS */
.nav-sidebar {
border-right: 1px solid #1c1e22;
}
/*! /*!
* bootswatch v3.3.7 * bootswatch v3.3.7
* Homepage: http://bootswatch.com * Homepage: http://bootswatch.com

View File

@ -1,4 +1,9 @@
@import url("https://fonts.googleapis.com/css?family=Roboto:400,700"); @import url("https://fonts.googleapis.com/css?family=Roboto:400,700");
/* Custom CSS */
.nav-sidebar {
border-right: 1px solid #282828;
}
/*! /*!
* bootswatch v3.3.7 * bootswatch v3.3.7
* Homepage: http://bootswatch.com * Homepage: http://bootswatch.com

View File

@ -4,7 +4,7 @@
{% block title %}Alerts{% endblock %} {% block title %}Alerts{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>Message</th> <th>Message</th>
@ -14,9 +14,15 @@
</thead> </thead>
<tbody> <tbody>
{% for alert in alerts %} {% for alert in alerts %}
<tr {% if alert.seen is True %}class="success"{% endif %} data-id="{{ alert.id }}"> <tr data-id="{{ alert.id }}">
<td>{{ alert.snippet }}</td> <td>{{ alert.snippet }}</td>
<td>{{ alert.seen }}</td> <td>
{% if alert.seen %}
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
{% else %}
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
{% endif %}
</td>
<td>{{ alert.date }}</td> <td>{{ alert.date }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -3,7 +3,7 @@
{% block title %}Applications{% endblock %} {% block title %}Applications{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>App ID</th> <th>App ID</th>
@ -16,12 +16,19 @@
</thead> </thead>
<tbody> <tbody>
{% for app in applications %} {% for app in applications %}
<tr {% if app.accepted is True %}class="success"{% endif %}{% if app.accepted is False %}class="danger"{% endif %} data-id="{{ app.id }}"> <tr data-id="{{ app.id }}">
<td>{{ app.id }}</td> <td>{{ app.id }}</td>
<td>{{ app.username }}</td> <td>{{ app.username }}</td>
<td>{{ app.age }}</td> <td>{{ app.age }}</td>
<td>{{ app.ever_banned }}</td> <td>{{ app.ever_banned }}</td>
<td>{{ app.status }}</td> <td>
{{ app.status }}
{% if app.accepted is True %}
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
{% elif app.accepted is False %}
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
{% endif %}
</td>
<td>{{ app.date }}</td> <td>{{ app.date }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -2,7 +2,7 @@
{% block title %}Bans{% endblock %} {% block title %}Bans{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>Player</th> <th>Player</th>

View File

@ -1,16 +0,0 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block title %}Bots{% endblock %}
{% block section %}
<div id="content">
{% for bot in bots %}
<p>{{ bot.name }}: <span class="label {% if bot.status is True %}label-success{% else %}label-danger{% endif %}">{{ bot.display }}</span></p>
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<button class="btn btn-primary{% if bot.status is True %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="start" {% if bot.status is True %} disabled="disabled"{% endif %}>Start</button>
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="stop" {% if bot.status is False %}disabled="disabled"{% endif %}>Stop</button>
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="restart" {% if bot.status is False %}disabled="disabled"{% endif %}>Restart</button>
</form>
<br/>
{% endfor %}
</div>
{% endblock section %}

View File

@ -28,6 +28,9 @@
<li class="dropdown"> <li class="dropdown">
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a> <a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown"> <ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown">
{% if user.is_staff %}
<li><a target="_blank" href="{% url "admin:index" %}">Admin Site</a></li>
{% endif %}
<li><a style="cursor:pointer;" data-toggle="modal" data-target="#settingsModal">Settings</a></li> <li><a style="cursor:pointer;" data-toggle="modal" data-target="#settingsModal">Settings</a></li>
<li><a style="cursor:pointer;" data-toggle="modal" data-target="#passwordModal">Change Password</a></li> <li><a style="cursor:pointer;" data-toggle="modal" data-target="#passwordModal">Change Password</a></li>
<li><a href="{% url "logout" %}">Logout</a></li> <li><a href="{% url "logout" %}">Logout</a></li>
@ -69,9 +72,6 @@
<form id="settingsForm" method="POST" action="{% url 'api-web' keyword='settings' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %} <form id="settingsForm" method="POST" action="{% url 'api-web' keyword='settings' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<div class="modal-body"> <div class="modal-body">
{% get_form 'usersettings' request %} {% get_form 'usersettings' request %}
{% if user.is_staff %}
<br/><a target="_blank" href="{% url "admin:index" %}">Admin Site</a>
{% endif %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>

View File

@ -0,0 +1,47 @@
<div id="addAttachmentModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Add Attachment(s)</h4>
</div>
<div class="modal-body">
<form method="post" id="add-attachment-form" enctype="multipart/form-data">{% csrf_token %}
<input type="file" multiple name="attachments"/>
<input type="hidden" name="next" value="{% url 'note_info' note.id %}"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="add-attachment-save">Save</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(() => {
const $modal = $('#addAttachmentModal');
const addAttachmentSave = document.querySelector('#add-attachment-save');
const addAttachmentForm = document.querySelector('#add-attachment-form');
document.querySelectorAll('.add-attachment[data-model][data-ref]').forEach((elem) => {
elem.addEventListener('click', () => {
addAttachmentModal(elem.dataset.model, elem.dataset.ref);
});
});
$modal.on('hidden.bs.modal', () => {
addAttachmentForm.reset();
});
addAttachmentSave.addEventListener('click', () => {
addAttachmentForm.submit();
});
function addAttachmentModal(model, ref) {
addAttachmentForm.action = '{% url 'attachment_add' 'X' 0 %}'
.replace('X', model)
.replace('0', ref);
$modal.modal('show');
}
});
</script>

View File

@ -0,0 +1,48 @@
<div id="deleteAttachmentModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Delete Attachment</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to delete <code id="delete-attachment-name"></code>?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-attachment-confirm">Confirm</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(() => {
const $modal = $('#deleteAttachmentModal');
const deleteAttachmentName = document.querySelector('#delete-attachment-name');
const deleteAttachmentConfirm = document.querySelector('#delete-attachment-confirm');
document.querySelectorAll('.delete-attachment[data-name][data-id]').forEach((elem) => {
elem.addEventListener('click', () => {
deleteAttachmentModal(elem.dataset.name, elem.dataset.id);
});
});
deleteAttachmentConfirm.addEventListener('click', () => {
const attachmentURL = '{% url 'attachment' 0 %}'.replace('0', deleteAttachmentConfirm.dataset.id);
fetch(attachmentURL, {
method: 'DELETE',
headers: {
'X-CSRFToken': '{{ csrf_token }}'
}
}).then(() => {
location.reload();
});
});
function deleteAttachmentModal(name, id) {
deleteAttachmentName.innerHTML = name;
deleteAttachmentConfirm.dataset.id = id;
$modal.modal('show');
}
});
</script>

View File

@ -2,24 +2,34 @@
{% block title %}Notes{% endblock %} {% block title %}Notes{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>Player</th> <th>Player</th>
<th>Message</th> <th>Message</th>
<th>Importance</th> <th>Importance</th>
<td>Issued By</td> <td>Issued By</td>
<td>Attachments</td>
<th>Date</th> <th>Date</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for note in notes %} {% for note in notes %}
<tr {% if note.importance == 'L' %}class="info"{% endif %}{% if note.importance == 'M' %}class="warning"{% endif %}{% if note.importance == 'H' %}class="danger"{% endif %} data-id="{{ note.id }}"> <tr data-id="{{ note.id }}">
<!-- <td>{{ note.id }}</td> --> <!-- <td>{{ note.id }}</td> -->
<td>{{ note.issuee }}</td> <td>{{ note.issuee }}</td>
<td>{{ note.snippet }}</td> <td>{{ note.snippet }}</td>
<td>{{ note.importance_display }}</td> <td>
<span class="label label-{% if note.importance == 'L' %}info{% elif note.importance == 'M' %}warning{% elif note.importance == 'H' %}danger{% endif %}">
{{ note.importance_display }}
</span>
</td>
<td>{{ note.issuer }}</td> <td>{{ note.issuer }}</td>
<td>
{% for attachment in note.attachments %}
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a><br/>
{% endfor %}
</td>
<td>{{ note.date }}</td> <td>{{ note.date }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -6,21 +6,17 @@
<h2 class="sub-header">New Note</h2> <h2 class="sub-header">New Note</h2>
<div class="row"> <div class="row">
<div class="col-xs-18 col-md-12"> <div class="col-xs-18 col-md-12">
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %} <form action="" method="post" enctype="multipart/form-data">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<p><label for="">Player Filter:</label> <input id="id_filter" type="text"/></p> <p><label for="id_filter">Player Filter:</label> <input id="id_filter" type="text"/></p>
{{ form }} {{ form }}
<p>
<label for="attachments">Attachments:</label>
<input id="attachments" name="attachments" type="file" multiple/>
</p>
<button id="saveButton" class="btn btn-primary" type="submit">Save</button> <button id="saveButton" class="btn btn-primary" type="submit">Save</button>
</form> </form>
</div> </div>
</div> </div>
<div class="row">
<div class="col-xs-6 col-md-4">
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div> </div>
<script> <script>
$(document).ready(function() { $(document).ready(function() {

View File

@ -15,7 +15,7 @@
<div class="col-xs-12 col-md-8"> <div class="col-xs-12 col-md-8">
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %} <form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<p>Issuer: {{ note.staff.username }} </p> <p>Issuer: {{ note.staff.username }} </p>
<p>Importance: <p><label for="noteImportance">Importance:</label>
{% if user.is_staff or user == note.staff %} {% if user.is_staff or user == note.staff %}
<select id="noteImportance" name="importance"> <select id="noteImportance" name="importance">
{% for p in form.importance %} {% for p in form.importance %}
@ -32,23 +32,44 @@
</form> </form>
</div> </div>
</div> </div>
{% if note.attachments.all|length > 0 %}
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-4"> <div class="col-xs-9 col-md-6">
<table>
<thead>
<tr><th>Attachments</th></tr>
</thead>
<tbody>
{% for attachment in note.attachments %}
<tr>
<td>
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
<span class="delete-attachment" data-name="{{ attachment.file_name }}" data-id="{{ attachment.id }}">
<i class="glyphicon glyphicon-remove-circle text-danger"></i>
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<br/>
<button type="button" class="btn btn-sm btn-primary add-attachment" data-model="N" data-ref="{{ note.id }}">Add Attachment(s)</button>
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div> </div>
<script> <script>
$("#saveButton").hide(); $("#saveButton").hide();
$("#noteImportance").change(function() { $("#noteImportance").change(function() {
if (("{{ user.username }}" == "{{ note.staff.username }}" || "{{ user.is_staff }}" == "True") && $(this).val() != "{{ note.importance }}") { if (("{{ user.username }}" === "{{ note.staff.username }}" || "{{ user.is_staff }}" === "True") && $(this).val() !== "{{ note.importance }}") {
$("#saveButton").show(); $("#saveButton").show();
} else { } else {
$("#saveButton").hide(); $("#saveButton").hide();
} }
}); });
</script> </script>
{% include 'minecraft_manager/modal/add_attachment.html' %}
{% include 'minecraft_manager/modal/delete_attachment.html' %}
{% endblock section %} {% endblock section %}

View File

@ -2,7 +2,7 @@
{% block title %}Players{% endblock %} {% block title %}Players{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>Username</th> <th>Username</th>
@ -13,10 +13,18 @@
</thead> </thead>
<tbody> <tbody>
{% for player in players %} {% for player in players %}
<tr {% if player.uuid in bans %}class="danger"{% endif %}{% if player.uuid not in bans %}class="success"{% endif %} data-id="{{ player.id }}"> <tr data-id="{{ player.id }}">
<td>{{ player.username }}{% if user.usersettings.search_player_ip is True %}<span hidden="hidden">{{ player.ips }}</span>{% endif %}</td> <td>{{ player.username }}{% if user.usersettings.search_player_ip is True %}<span hidden="hidden">{{ player.ips }}</span>{% endif %}</td>
<td>{{ player.uuid }}</td> <td>{{ player.uuid }}</td>
<td>{% if player.uuid in bans %}True {% else %}False{% endif %}</td> <td>
{% if player.uuid in bans %}
Yes
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
{% else %}
No
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
{% endif %}
</td>
<td>{{ player.last_seen }}</td> <td>{{ player.last_seen }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -6,12 +6,12 @@
<h2 class="sub-header">Player Info ({{ player.username }})</h2> <h2 class="sub-header">Player Info ({{ player.username }})</h2>
<div class="row"> <div class="row">
<div class="col-xs-9 col-md-6"> <div class="col-xs-9 col-md-6">
<p>UUID: {{ player.uuid }}</p> <p>UUID: <code>{{ player.uuid }}</code> <button data-copy="{{ player.uuid }}" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-copy"></i></button></p>
{% if player.auth_user %} {% if player.auth_user %}
<p>Connected User: {{ player.auth_user.username }}</p> <p>Connected User: {{ player.auth_user.username }}</p>
{% endif %} {% endif %}
{% if player.application %} {% if player.application %}
<p>Application: <a href="{% url "application" %}{{ player.application.id }}">Here</a></p> <p>Application: <a href="{% url "application" %}{{ player.application.id }}">{{ player.application.username }}</a></p>
{% endif %} {% endif %}
<p>Donor Status: {{ player.donor_status }}</p> <p>Donor Status: {{ player.donor_status }}</p>
<p>First Seen: {{ player.first_seen }}</p> <p>First Seen: {{ player.first_seen }}</p>
@ -20,14 +20,35 @@
</div> </div>
<div class="col-xs-9 col-md-6"> <div class="col-xs-9 col-md-6">
<h4>Tickets</h4> <h4>Tickets</h4>
<table class="table table-hover link-table"> <table class="table table-striped table-hover link-table">
<thead>
<tr>
<td>ID</td>
<td>Message</td>
<td>Resolved</td>
<td>Attachments</td>
</tr>
</thead>
<tbody> <tbody>
{% if form.tickets %} {% if form.tickets %}
{% for ticket in form.tickets %} {% for ticket in form.tickets %}
<tr {% if ticket.resolved is True %}class="success"{% else %}class="danger"{% endif %} data-id="{{ ticket.id }}" data-url="{% url "ticket" %}"> <tr data-id="{{ ticket.id }}" data-url="{% url "ticket" %}">
<td>{{ ticket.id }}</td> <td>{{ ticket.id }}</td>
<td>{{ ticket.snippet }}</td> <td>{{ ticket.snippet }}</td>
<td>{{ ticket.resolved }}</td> <td>
{% if ticket.resolved is True %}
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
{% else %}
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
{% endif %}
</td>
<td>
{% for note in ticket.notes %}
{% for attachment in note.attachments %}
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a> ({{ note.author.username }})<br/>
{% endfor %}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% else %} {% else %}
@ -36,15 +57,33 @@
</tbody> </tbody>
</table> </table>
<br/> <br/>
<table class="table table-hover link-table">
<h4>Notes</h4> <h4>Notes</h4>
<table class="table table-striped table-hover link-table">
<thead>
<tr>
<td>Message</td>
<td>Importance</td>
<td>Attachments</td>
</tr>
</thead>
<tbody> <tbody>
{% if form.notes %} {% if form.notes %}
{% for note in form.notes %} {% for note in form.notes %}
<tr {% if note.importance == 'L' %}class="info"{% endif %}{% if note.importance == 'M' %}class="warning"{% endif %}{% if note.importance == 'H' %}class="danger"{% endif %} data-id="{{ note.id }}" data-url="{% url "note" %}"> <tr data-id="{{ note.id }}" data-url="{% url "note" %}">
<!-- {{ note.id }} --> <!-- {{ note.id }} -->
<td>{{ note.snippet }}</td> <td>{{ note.snippet }}</td>
<td>{{ note.importance_display }}</td> <td>
<span class="label label-{% if note.importance == 'L' %}info{% elif note.importance == 'M' %}warning{% elif note.importance == 'H' %}danger{% endif %}">
{{ note.importance_display }}
</span>
</td>
<td>
{% for attachment in note.attachments %}
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
{% if note.staff %}({{ note.staff.username }}){% endif %}
<br/>
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% else %} {% else %}
@ -54,14 +93,22 @@
</table> </table>
<a class="btn btn-primary" href="{% url 'note_add' %}?player={{ player.id }}">Add Note</a> <a class="btn btn-primary" href="{% url 'note_add' %}?player={{ player.id }}">Add Note</a>
<br/><br/> <br/><br/>
<table class="table table-striped table-hover link-table">
<h4>IPs</h4> <h4>IPs</h4>
<table class="table table-striped table-hover link-table">
<thead>
<tr>
<th>IP</th>
<th>Last Used</th>
<th>Associated Users</th>
</tr>
</thead>
<tbody> <tbody>
{% if form.ips %} {% if form.ips %}
{% for ip in form.ips %} {% for ip in form.ips %}
<tr class="default" data-id="" data-url="{% url 'ip' ip.id %}"> <tr class="default" data-id="" data-url="{% url 'ip' ip.id %}">
<!-- {{ ip.id }} --> <!-- {{ ip.id }} -->
<td>{{ ip.ip }} ({{ ip.last_used_formatted }})</td> <td>{{ ip.ip }}</td>
<td>{{ ip.last_used_formatted }}</td>
{% if ip.associated %} {% if ip.associated %}
<td> <td>
{% for assoc in ip.associated %} {% for assoc in ip.associated %}
@ -81,4 +128,21 @@
</div> </div>
</div> </div>
</div> </div>
<script>
document.querySelectorAll('[data-copy]').forEach((elem) => {
elem.addEventListener('click', () => {
const text = document.createElement('textarea');
text.value = elem.dataset.copy;
text.style.top = "0";
text.style.left = "0";
text.style.position = "fixed";
document.body.appendChild(text);
text.focus();
text.select();
document.execCommand('copy');
text.remove();
});
});
</script>
{% endblock section %} {% endblock section %}

View File

@ -2,7 +2,7 @@
{% block title %}Tickets{% endblock %} {% block title %}Tickets{% endblock %}
{% block section %} {% block section %}
<div id="content" hidden="hidden"> <div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table"> <table id="model-table" class="table table-striped table-hover link-table">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th>ID</th>
@ -16,13 +16,23 @@
</thead> </thead>
<tbody> <tbody>
{% for ticket in tickets %} {% for ticket in tickets %}
<tr {% if ticket.resolved is True %}class="info"{% elif ticket.priority == 'L' %}class="success"{% elif ticket.priority == 'M' %}class="warning"{% elif ticket.priority == 'H' %}class="danger"{% endif %} data-id="{{ ticket.id }}"> <tr data-id="{{ ticket.id }}">
<td>{{ ticket.id }}</td> <td>{{ ticket.id }}</td>
<td>{{ ticket.issuer }}</td> <td>{{ ticket.issuer }}</td>
<td>{{ ticket.snippet }}</td> <td>{{ ticket.snippet }}</td>
<td>{{ ticket.priority_display }}</td> <td>
<span class="label label-{% if ticket.priority == 'L' %}info{% elif ticket.priority == 'M' %}warning{% elif ticket.priority == 'H' %}danger{% endif %}">
{{ ticket.priority_display }}
</span>
</td>
<td>{{ ticket.claimed_by }}</td> <td>{{ ticket.claimed_by }}</td>
<td>{{ ticket.resolved }}</td> <td>
{% if ticket.resolved %}
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
{% else %}
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
{% endif %}
</td>
<td>{{ ticket.date }}</td> <td>{{ ticket.date }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -74,20 +74,42 @@
{% endif %} {% endif %}
<p>Created: {{ ticket_note.date }}</p> <p>Created: {{ ticket_note.date }}</p>
<p>Last Update: {{ ticket_note.last_update }}</p> <p>Last Update: {{ ticket_note.last_update }}</p>
{% if ticket_note.attachments|length > 0 %}
<table>
<thead>
<tr><th>Attachments</th></tr>
</thead>
<tbody>
{% for attachment in ticket_note.attachments %}
<tr>
<td>
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
<span class="delete-attachment" data-name="{{ attachment.file_name }}" data-id="{{ attachment.id }}">
<i class="glyphicon glyphicon-remove-circle text-danger"></i>
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if not form.has_ticket_note and not form.show_ticket_note %} {% if not form.has_ticket_note and not form.show_ticket_note %}
<div id="createDiv" class="row"> <div id="createDiv" class="row">
<button class="btn btn-primary" onClick="showNote()">Create Note</button> <button class="btn btn-primary" onClick="showNote();">Create Note</button>
</div> </div>
{% endif %} {% endif %}
<div id="ticket_noteDiv" class="row" {% if not form.show_ticket_note %}style="display: none;"{% endif %}> <div id="ticket_noteDiv" class="row" {% if not form.show_ticket_note %}style="display: none;"{% endif %}>
<br/> <br/>
<h3>Note</h3> <h3>Note</h3>
<form action="" method="POST">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %} <form action="" method="POST" enctype="multipart/form-data">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
{{ form.ticket_note_form }} {{ form.ticket_note_form }}
<br/> <p>
<label for="attachments">Attachments:</label>
<input id="attachments" name="attachments" type="file" multiple/>
</p>
<button type="submit" class="btn btn-primary" name="ticket_note" value="{% if form.has_ticket_note %}edit{% else %}create{% endif %}">Save</button> <button type="submit" class="btn btn-primary" name="ticket_note" value="{% if form.has_ticket_note %}edit{% else %}create{% endif %}">Save</button>
</form> </form>
</div> </div>
@ -97,7 +119,7 @@
$("#saveButton").hide(); $("#saveButton").hide();
$("#ticketStaff").change(function() { $("#ticketStaff").change(function() {
var $priority = $("#ticketPriority"); var $priority = $("#ticketPriority");
if ($(this).val() != "{{ ticket.staff.id }}" || $priority.val() != "{{ ticket.priority }}") { if ($(this).val() !== "{{ ticket.staff.id }}" || $priority.val() !== "{{ ticket.priority }}") {
toggleSave(true); toggleSave(true);
} else { } else {
toggleSave(false); toggleSave(false);
@ -105,7 +127,7 @@
}); });
$("#ticketPriority").change(function() { $("#ticketPriority").change(function() {
var $staff = $("#ticketStaff"); var $staff = $("#ticketStaff");
if ($(this).val() != "{{ ticket.priority }}" || $staff.val() != "{{ ticket.staff.id }}") { if ($(this).val() !== "{{ ticket.priority }}" || $staff.val() !== "{{ ticket.staff.id }}") {
toggleSave(true); toggleSave(true);
} else { } else {
toggleSave(false); toggleSave(false);
@ -113,10 +135,10 @@
}); });
function toggleSave(toggle) { function toggleSave(toggle) {
if (toggle == true) { if (toggle === true) {
$("#resolveButton").hide(); $("#resolveButton").hide();
$("#saveButton").show(); $("#saveButton").show();
} else if (toggle == false) { } else if (toggle === false) {
$("#resolveButton").show(); $("#resolveButton").show();
$("#saveButton").hide(); $("#saveButton").hide();
} }
@ -128,4 +150,5 @@
$("#editBtn").hide(); $("#editBtn").hide();
} }
</script> </script>
{% include 'minecraft_manager/modal/delete_attachment.html' %}
{% endblock section %} {% endblock section %}

38
urls.py
View File

@ -7,37 +7,41 @@ urlpatterns = [
url(r'^$', RedirectView.as_view(pattern_name='overview')), url(r'^$', RedirectView.as_view(pattern_name='overview')),
# Dashboard # Dashboard
url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()), name="overview"), url(r'^overview/$', login_required(mcm.Overview.as_view()), name="overview"),
url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()), name="ban"), url(r'^ban/$', login_required(mcm.Ban.as_view()), name="ban"),
# Alerts # Alerts
url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()), name="alert"), url(r'^alert/$', login_required(mcm.Alert.as_view()), name="alert"),
url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view())), url(r'^alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view())),
# Applications # Applications
url(r'^dashboard/application/$', login_required(mcm.Application.as_view()), name="application"), url(r'^application/$', login_required(mcm.Application.as_view()), name="application"),
url(r'^dashboard/reference/$', login_required(mcm.Reference.as_view()), name="reference"), url(r'^reference/$', login_required(mcm.Reference.as_view()), name="reference"),
url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view())), url(r'^application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view())),
# Players # Players
url(r'^dashboard/player/$', login_required(mcm.Player.as_view()), name="player"), url(r'^player/$', login_required(mcm.Player.as_view()), name="player"),
url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view())), url(r'^player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view())),
# Tickets # Tickets
url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()), name="ticket"), url(r'^ticket/$', login_required(mcm.Ticket.as_view()), name="ticket"),
url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view())), url(r'^ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view())),
# Warnings # Notes
url(r'^dashboard/note/$', login_required(mcm.Note.as_view()), name="note"), url(r'^note/$', login_required(mcm.Note.as_view()), name="note"),
url(r'^dashboard/note/(?P<note_id>[0-9]{1,5})/$', login_required(mcm.NoteInfo.as_view())), url(r'^note/(?P<note_id>[0-9]{1,5})/$', login_required(mcm.NoteInfo.as_view()), name='note_info'),
url(r'^dashboard/note/add$', login_required(mcm.NoteAdd.as_view()), name="note_add"), url(r'^note/add$', login_required(mcm.NoteAdd.as_view()), name="note_add"),
# Attachments
url(r'^attachment/(?P<attachment_id>[0-9]{1,5})/$', login_required(mcm.Attachment.as_view()), name="attachment"),
url(r'attachment/(?P<ref_model>[A-Za-z])/(?P<ref_id>[0-9]{1,5})/$', login_required(mcm.AddAttachment.as_view()), name='attachment_add'),
# IP # IP
url(r'^dashboard/ip/(?P<ip_id>[0-9]{1,5})/$', login_required(mcm.IP.as_view()), name="ip"), url(r'^ip/(?P<ip_id>[0-9]{1,5})/$', login_required(mcm.IP.as_view()), name="ip"),
# Report # Report
url(r'^report/$', login_required(mcm.Report.as_view()), name="report"), url(r'^report/$', login_required(mcm.Report.as_view()), name="report"),
# Chat # Chat
url(r'^dashboard/chat/$', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"), url(r'^chat/$', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"),
] ]

View File

@ -1,5 +1,6 @@
import discord, requests import discord, requests
from django.conf import settings from django.conf import settings
from django.core.files.storage import FileSystemStorage
from minecraft_manager.models import Player, Application from minecraft_manager.models import Player, Application
@ -32,8 +33,9 @@ def build_application(application):
embed.add_field(name="Age", value=application.age) embed.add_field(name="Age", value=application.age)
embed.add_field(name="Type of Player", value=application.player_type) embed.add_field(name="Type of Player", value=application.player_type)
embed.add_field(name="Ever been banned", value=application.ever_banned) embed.add_field(name="Ever been banned", value=application.ever_banned)
if application.ever_banned: if application.ever_banned and application.ever_banned_explanation:
embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation) embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation)
if application.reference:
embed.add_field(name="Reference", value=application.reference) embed.add_field(name="Reference", value=application.reference)
embed.add_field(name="Read the Rules", value=application.read_rules) embed.add_field(name="Read the Rules", value=application.read_rules)
embed.add_field(name="Date", value=application.date_display) embed.add_field(name="Date", value=application.date_display)

View File

@ -1,18 +1,16 @@
#create your views here.
# https://api.mojang.com/users/profiles/minecraft/<username>
from __future__ import absolute_import from __future__ import absolute_import
import json, datetime, pytz, os, sys import json, datetime, pytz, os
from django.utils import timezone from django.utils import timezone
from itertools import chain from itertools import chain
from django.http import JsonResponse from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, reverse, redirect from django.shortcuts import render, reverse, redirect
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.conf import settings from django.conf import settings
from django.views.generic import View from django.views.generic import View
from django.contrib.auth.models import User from django.contrib.auth.models import User
from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, TicketNote as TicketNoteModel, Note as NoteModel, IP as IPModel, Alert as AlertModel, UserSettings as UserSettingsModel from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, TicketNote as TicketNoteModel, Note as NoteModel, IP as IPModel, Alert as AlertModel, UserSettings as UserSettingsModel, Attachment as AttachmentModel, RefModels
from minecraft_manager.forms import TicketNoteForm, NoteForm from minecraft_manager.forms import TicketNoteForm, NoteForm
from minecraft_manager.overview import overview_data from minecraft_manager.overview import overview_data
from minecraft_manager.utils import resolve_player from minecraft_manager.utils import resolve_player
@ -280,6 +278,11 @@ class TicketInfo(View):
db.message = n.message db.message = n.message
db.last_update = timezone.now() db.last_update = timezone.now()
db.save() db.save()
# Refresh to get the ID for attachments
note = TicketNoteModel.objects.get(ticket=ticket, author=request.user)
for file in request.FILES.getlist('attachments', []):
attachment = AttachmentModel(ref_model=RefModels.TICKET_NOTE[0], ref_id=note.id, file=file)
attachment.save()
else: else:
show_ticket_note = True show_ticket_note = True
else: else:
@ -314,23 +317,30 @@ class NoteInfo(View):
def get(self, request, note_id): def get(self, request, note_id):
note = NoteModel.objects.get(id=note_id) note = NoteModel.objects.get(id=note_id)
form = {'importance': NoteModel.IMPORTANCE} form = {'importance': NoteModel.IMPORTANCE}
return render(request, 'minecraft_manager/note_info.html', {'current_app': 'note', 'form': form, 'note': note}) return render(request, 'minecraft_manager/note_info.html', {
'current_app': 'note',
'form': form,
'note': note
})
def post(self, request, note_id): def post(self, request, note_id):
post = request.POST post = request.POST
note = NoteModel.objects.get(id=note_id) note = NoteModel.objects.get(id=note_id)
if 'importance' in post: if 'importance' in post:
API.discord_mcm("Note #**{0}**'s importance was changed from {1} to {2} by {3}".format(note.id, API.discord_mcm("Note #**{0}**'s importance was changed from {1} to {2} by {3}".format(
note.id,
note.importance_display, note.importance_display,
NoteModel.importance_code_to_display( NoteModel.importance_code_to_display(post['importance']),
post[ request.user.player.username)
'importance']), )
request.user.player.username))
note.importance = post['importance'] note.importance = post['importance']
note.save() note.save()
form = {'importance': NoteModel.IMPORTANCE} form = {'importance': NoteModel.IMPORTANCE}
return render(request, 'minecraft_manager/note_info.html', return render(request, 'minecraft_manager/note_info.html', context={
{'current_app': 'note', 'form': form, 'note': note}) 'current_app': 'note',
'form': form,
'note': note
})
class NoteAdd(View): class NoteAdd(View):
@ -341,8 +351,7 @@ class NoteAdd(View):
form = NoteForm() form = NoteForm()
if 'player' in get: if 'player' in get:
form.initial = {'player': get['player']} form.initial = {'player': get['player']}
return render(request, 'minecraft_manager/note_add.html', return render(request, 'minecraft_manager/note_add.html', context={'current_app': 'note', 'form': form.as_p()})
{'current_app': 'note', 'form': form.as_p()})
def post(self, request): def post(self, request):
post = request.POST post = request.POST
@ -352,14 +361,41 @@ class NoteAdd(View):
note.staff = request.user note.staff = request.user
note.save() note.save()
API.discord_mcm( API.discord_mcm(
"**{0}** made a **{1}** importance note for **{2}**\nPreview: {3}".format(note.staff.player.username, "**{0}** made a **{1}** importance note for **{2}**\nPreview: {3}".format(
note.staff.player.username,
note.importance_display, note.importance_display,
note.player.username, note.player.username,
note.snippet)) note.snippet)
)
for file in request.FILES.getlist('attachments', []):
attachment = AttachmentModel(ref_model=RefModels.NOTE[0], ref_id=note.id, file=file)
attachment.save()
return redirect("{0}{1}".format(reverse('note'), note.id)) return redirect("{0}{1}".format(reverse('note'), note.id))
else: else:
return render(request, 'minecraft_manager/note_add.html', return render(request, 'minecraft_manager/note_add.html', context={'current_app': 'note', 'form': form})
{'current_app': 'note', 'form': form})
class Attachment(View):
def get(self, request, attachment_id):
attachment = AttachmentModel.objects.get(id=attachment_id)
resp = HttpResponse(attachment.file)
resp['Content-Disposition'] = f"attachment; filename={attachment.file_name}"
return resp
def delete(self, request, attachment_id):
attachment = AttachmentModel.objects.get(id=attachment_id)
attachment.delete()
return HttpResponse(status=204)
class AddAttachment(View):
def post(self, request, ref_model, ref_id):
for file in request.FILES.getlist('attachments', []):
attachment = AttachmentModel(ref_model=ref_model, ref_id=ref_id, file=file)
attachment.save()
return redirect(request.POST.get('next', reverse('overview')))
class IP(View): class IP(View):
@ -427,7 +463,6 @@ class Report(View):
class Chat(View): class Chat(View):
@staticmethod @staticmethod
def replace_ascii(message): def replace_ascii(message):
return message.replace(" ", "\\040").replace("\"", "\\042").replace("#", "\\043").replace("$", "\\044")\ return message.replace(" ", "\\040").replace("\"", "\\042").replace("#", "\\043").replace("$", "\\044")\