diff --git a/__init__.py b/__init__.py index e69de29..4527f3e 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1 @@ +default_app_config = 'minecraft_manager.apps.MinecraftManagerAppConfig' diff --git a/admin.py b/admin.py index 6cc4532..5de50b1 100644 --- a/admin.py +++ b/admin.py @@ -4,7 +4,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User 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 @@ -90,6 +90,14 @@ class IPAdmin(admin.ModelAdmin): 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: admin.site.unregister(User) admin.site.register(User, UserAdmin) @@ -101,6 +109,7 @@ try: admin.site.register(Player, PlayerAdmin) admin.site.register(IP, IPAdmin) admin.site.register(Alert) + admin.site.register(Attachment, AttachmentAdmin) api_register() except admin.sites.AlreadyRegistered: pass diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..a5fc31c --- /dev/null +++ b/apps.py @@ -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) + diff --git a/bot/commands.py b/bot/commands.py index 9d6f4c1..dd7c2ec 100644 --- a/bot/commands.py +++ b/bot/commands.py @@ -153,6 +153,29 @@ class Commands(commands.Cog): 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?") + @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() async def search(self, ctx, search: str): await self._search(ctx, search) diff --git a/forms.py b/forms.py index d0a37b3..d16ab7c 100644 --- a/forms.py +++ b/forms.py @@ -30,7 +30,7 @@ class TicketForm(ModelForm): fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z'] widgets = { 'player': TextInput, - 'message': Textarea, + 'message': Textarea(attrs={'style': 'display: block;'}), } @@ -39,7 +39,7 @@ class NoteForm(ModelForm): model = Note fields = ['player', 'message', 'importance'] widgets = { - 'message': Textarea + 'message': Textarea(attrs={'style': 'display: block;'}) } diff --git a/migrations/0015_auto_20210206_2140.py b/migrations/0015_auto_20210206_2140.py new file mode 100644 index 0000000..fcb0e44 --- /dev/null +++ b/migrations/0015_auto_20210206_2140.py @@ -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?'), + ), + ] diff --git a/models.py b/models.py index db11175..ad6ea38 100644 --- a/models.py +++ b/models.py @@ -1,13 +1,13 @@ from django.db import models from django.contrib.auth.models import User from django.db.models import Q +from os.path import basename import logging, yaml, pytz, json, os from django.conf import settings from datetime import datetime logger = logging.getLogger(__name__) - class MinecraftManagerUser(User): class Meta: @@ -252,6 +252,10 @@ class Ticket(models.Model): def date_display(self): return str(self.date).split(".")[0] + @property + def notes(self): + return TicketNote.objects.filter(ticket=self) + def __str__(self): return "{}: {}".format(self.issuer, self.snippet) @@ -263,6 +267,10 @@ class TicketNote(models.Model): last_update = 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: verbose_name = "Ticket Note" verbose_name_plural = "Ticket Notes" @@ -324,10 +332,49 @@ class Note(models.Model): def date_display(self): 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): 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): def get_queryset(self): users = User.objects.filter(is_active=True) diff --git a/signals/__init__.py b/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/signals/pre_delete.py b/signals/pre_delete.py new file mode 100644 index 0000000..cb46ea4 --- /dev/null +++ b/signals/pre_delete.py @@ -0,0 +1,2 @@ +def attachment_delete(sender, instance, **kwargs): + instance.file.delete(False) diff --git a/static/minecraft_manager/css/bootswatch-darkly.css b/static/minecraft_manager/css/bootswatch-darkly.css index 9a5e710..b886c76 100644 --- a/static/minecraft_manager/css/bootswatch-darkly.css +++ b/static/minecraft_manager/css/bootswatch-darkly.css @@ -1,4 +1,9 @@ @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 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-flatly.css b/static/minecraft_manager/css/bootswatch-flatly.css index ba7e9e9..3e0f8f7 100644 --- a/static/minecraft_manager/css/bootswatch-flatly.css +++ b/static/minecraft_manager/css/bootswatch-flatly.css @@ -1,4 +1,9 @@ @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 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-slate.css b/static/minecraft_manager/css/bootswatch-slate.css index eb90344..599a694 100644 --- a/static/minecraft_manager/css/bootswatch-slate.css +++ b/static/minecraft_manager/css/bootswatch-slate.css @@ -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 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-solar.css b/static/minecraft_manager/css/bootswatch-solar.css index 3beb663..b9b0b72 100644 --- a/static/minecraft_manager/css/bootswatch-solar.css +++ b/static/minecraft_manager/css/bootswatch-solar.css @@ -1,4 +1,9 @@ @import url("https://fonts.googleapis.com/css?family=Roboto:400,700"); +/* Custom CSS */ +.nav-sidebar { + border-right: 1px solid #282828; +} + /*! * bootswatch v3.3.7 * Homepage: http://bootswatch.com diff --git a/templates/minecraft_manager/alert.html b/templates/minecraft_manager/alert.html index 736900a..d84c66a 100644 --- a/templates/minecraft_manager/alert.html +++ b/templates/minecraft_manager/alert.html @@ -4,7 +4,7 @@ {% block title %}Alerts{% endblock %} {% block section %}