Basic utils for models, added some stuff to the bot

This commit is contained in:
Dustin Pianalto 2019-09-19 21:52:16 -08:00
parent fb9744a93a
commit b3eefa1266
46 changed files with 1271 additions and 149 deletions

View File

@ -78,6 +78,7 @@ class Geeksbot(commands.Bot):
self.config_dir = 'geeksbot/config/' self.config_dir = 'geeksbot/config/'
self.config_file = 'bot_config.json' self.config_file = 'bot_config.json'
self.extension_dir = 'exts' self.extension_dir = 'exts'
self.api_token = os.environ['API_TOKEN']
self.aio_session = aiohttp.ClientSession(loop=self.loop) self.aio_session = aiohttp.ClientSession(loop=self.loop)
with open(f'{self.config_dir}{self.config_file}') as f: with open(f'{self.config_dir}{self.config_file}') as f:
self.bot_config = json.load(f) self.bot_config = json.load(f)
@ -90,6 +91,16 @@ class Geeksbot(commands.Bot):
self.git_url = 'https://github.com/dustinpianalto/geeksbot_v2' self.git_url = 'https://github.com/dustinpianalto/geeksbot_v2'
self.load_default_extensions() self.load_default_extensions()
self.book_emojis = {
'unlock': '🔓',
'start': '',
'back': '',
'hash': '#\N{COMBINING ENCLOSING KEYCAP}',
'forward': '',
'end': '',
'close': '🇽',
}
async def load_ext(self, mod): async def load_ext(self, mod):
self.load_extension(f'geeksbot.{self.extension_dir}.{mod}') self.load_extension(f'geeksbot.{self.extension_dir}.{mod}')
logger.info(f'Extension Loaded: {mod}') logger.info(f'Extension Loaded: {mod}')

View File

@ -64,9 +64,9 @@ class Exec(commands.Cog):
ret = await func() ret = await func()
except Exception: except Exception:
pag.add(stdout.getvalue()) pag.add(stdout.getvalue())
pag.add(traceback.format_exc()) pag.add(f'\n\uFFF8{traceback.format_exc()}')
for page in pag.pages(): book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message))
await ctx.send(page) await book.create_book()
else: else:
value = stdout.getvalue() value = stdout.getvalue()
# noinspection PyBroadException # noinspection PyBroadException
@ -76,10 +76,10 @@ class Exec(commands.Cog):
pass pass
value = format_output(value) value = format_output(value)
pag.add(value) pag.add(value)
pag.add(f'\nReturned: {ret}') pag.add(f'\n\uFFF8Returned: {ret}')
self._last_result = ret self._last_result = ret
for page in pag.pages(): book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message))
await ctx.send(page) await book.create_book()
@commands.command(hidden=True) @commands.command(hidden=True)
async def repl(self, ctx): async def repl(self, ctx):
@ -161,8 +161,8 @@ class Exec(commands.Cog):
body = self.cleanup_code(body) body = self.cleanup_code(body)
pag = Paginator(self.bot) pag = Paginator(self.bot)
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 120)) pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 120))
for page in pag.pages(): book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message))
await ctx.send(page) await book.create_book()
await ctx.message.add_reaction('') await ctx.message.add_reaction('')
except asyncio.TimeoutError: except asyncio.TimeoutError:
await ctx.send(f"Command did not complete in the time allowed.") await ctx.send(f"Command did not complete in the time allowed.")

View File

@ -156,7 +156,7 @@ class Paginator:
if not self._embed: if not self._embed:
for part in [str(p) for p in self._parts]: for part in [str(p) for p in self._parts]:
if part == self._page_break: if part.startswith(self._page_break):
close_page() close_page()
new_chars = len(_page) + len(part) new_chars = len(_page) + len(part)
@ -185,9 +185,8 @@ class Paginator:
open_field('\uFFF0') open_field('\uFFF0')
for part in [str(p) for p in self._parts]: for part in [str(p) for p in self._parts]:
if part.strip() == self._page_break: if part.strip().startswith(self._page_break):
close_page() close_page()
continue
elif part == self._field_break: elif part == self._field_break:
if len(_fields) + 1 < 25: if len(_fields) + 1 < 25:
close_field(next_name='\uFFF0') close_field(next_name='\uFFF0')

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ChannelsConfig(AppConfig):
name = 'channels'

View File

@ -0,0 +1,46 @@
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from geeksbot_v2.guilds.models import Guild
from .utils import create_error_response
from .utils import create_success_response
# Create your models here.
class Channel(models.Model):
id = models.CharField(max_length=30, primary_key=True)
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
@classmethod
def add_new_channel(cls, data):
id = data.get('id')
if id and cls.get_channel_by_id(id):
return create_error_response('Channel Already Exists',
status=status.HTTP_409_CONFLICT)
guild_id = data.get('guild')
if not (id and guild_id):
return create_error_response('Id and Guild are required',
status=status.HTTP_400_BAD_REQUEST)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
channel = cls(
id=id,
guild=guild
)
channel.save()
return create_success_response(channel, status.HTTP_201_CREATED, many=False)
@classmethod
def get_channel_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
def __str__(self):
return str(id)

View File

@ -0,0 +1,9 @@
from rest_framework import serializers
from .models import Channel
class ChannelSerializer(serializers.ModelSerializer):
class Meta:
model = Channel
fields = "__all__"

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,14 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_response(channel_data, status, many: bool = False):
from .serializers import ChannelSerializer
return Response(ChannelSerializer(channel_data, many=many).data,
status=status)

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -43,6 +43,7 @@ LOCALE_PATHS = [ROOT_DIR.path("locale")]
# https://docs.djangoproject.com/en/dev/ref/settings/#databases # https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {"default": env.db("DATABASE_URL")} DATABASES = {"default": env.db("DATABASE_URL")}
DATABASES["default"]["ATOMIC_REQUESTS"] = True DATABASES["default"]["ATOMIC_REQUESTS"] = True
DATABASES['default']['CONN_MAX_AGE'] = 0
# URLS # URLS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -263,6 +264,9 @@ ACCOUNT_EMAIL_VERIFICATION = "optional"
ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter" ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter" SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter"
ACCOUNT_FORMS = {
'signup': 'geeksbot_v2.users.forms.UserCreateForm',
}
# Your stuff... # Your stuff...
@ -275,6 +279,9 @@ REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": [ "DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer", "rest_framework.renderers.JSONRenderer",
], ],
"DEFAULT_PARSER_CLASSES": [
'rest_framework.parsers.JSONParser',
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 100, "PAGE_SIZE": 100,
} }

View File

@ -1,6 +1,5 @@
""" """
To understand why this file is here, please read: To understand why this file is here, please read:
http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
""" """
from django.conf import settings from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.4 on 2019-09-16 05:23 # Generated by Django 2.2.4 on 2019-09-17 19:31
from django.conf import settings from django.conf import settings
import django.contrib.postgres.fields import django.contrib.postgres.fields
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('guilds', '0001_initial'), ('guilds', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [

View File

@ -1,8 +1,18 @@
from datetime import datetime
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from rest_framework import status
from geeksbot_v2.guilds.models import Guild from geeksbot_v2.guilds.models import Guild
from geeksbot_v2.guilds.models import Role
from geeksbot_v2.users.models import User from geeksbot_v2.users.models import User
from geeksbot_v2.channels.models import Channel
from .utils import create_error_response
from .utils import create_success_response
from .utils import create_request_success_response
from .utils import create_comment_success_response
# Create your models here. # Create your models here.
@ -11,18 +21,124 @@ class Message(models.Model):
id = models.CharField(max_length=30, primary_key=True) id = models.CharField(max_length=30, primary_key=True)
author = models.ForeignKey(User, on_delete=models.CASCADE) author = models.ForeignKey(User, on_delete=models.CASCADE)
guild = models.ForeignKey(Guild, on_delete=models.CASCADE) guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
channel = models.CharField(max_length=30) channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
created_at = models.DateTimeField() created_at = models.DateTimeField()
modified_at = models.DateTimeField(null=True) modified_at = models.DateTimeField(null=True, blank=True)
deleted_at = models.DateTimeField(null=True) deleted_at = models.DateTimeField(null=True, blank=True)
content = models.CharField(max_length=2000) content = models.CharField(max_length=2000)
previous_content = ArrayField(models.CharField(max_length=2000)) previous_content = ArrayField(models.CharField(max_length=2000), default=[])
tagged_users = ArrayField(models.CharField(max_length=30)) tagged_users = models.ManyToManyField(User)
tagged_channels = ArrayField(models.CharField(max_length=30)) tagged_channels = models.ManyToManyField(Channel)
tagged_roles = ArrayField(models.CharField(max_length=30)) tagged_roles = models.ManyToManyField(Role)
tagged_everyone = models.BooleanField() tagged_everyone = models.BooleanField()
embeds = ArrayField(models.TextField()) embeds = ArrayField(models.TextField(), default=[])
previous_embeds = ArrayField(ArrayField(models.TextField())) previous_embeds = ArrayField(ArrayField(models.TextField()), default=[])
@classmethod
def add_new_message(cls, data):
id = data.get('id')
if id and cls.get_message_by_id(id):
return create_error_response("Message Already Exists",
status=status.HTTP_409_CONFLICT)
author_id = data.get('author')
guild_id = data.get('guild')
channel_id = data.get('channel')
created_at = data.get('created_at')
content = data.get('content')
tagged_everyone = data.get('tagged_everyone')
if not (id and author_id and guild_id and channel_id and created_at and content and tagged_everyone):
return create_error_response("One or more required fields are missing.",
status=status.HTTP_400_BAD_REQUEST)
author = User.get_user_by_id(author_id)
if not isinstance(author, User):
return create_error_response("Author Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response("Guild Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
channel = Channel.get_channel_by_id(channel_id)
if not isinstance(channel, Channel):
return create_error_response("Channel Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
created_at = datetime.fromtimestamp(created_at)
message = cls(
id=id,
author=author,
guild=guild,
channel=channel,
created_at=created_at,
tagged_everyone=tagged_everyone or False,
content=content or '',
embeds=data.get('embeds') or []
)
message.save()
if data.get('tagged_users'):
tagged_users = data.get('tagged_users')
for user_id in tagged_users:
user = User.get_user_by_id(user_id)
if user:
message.tagged_users.add(user)
if data.get('tagged_roles'):
tagged_roles = data.get('tagged_roles')
for role_id in tagged_roles:
role = Role.get_role_by_id(role_id)
if role:
message.tagged_roles.add(role)
if data.get('tagged_channels'):
tagged_channels = data.get('tagged_channels')
for channel_id in tagged_channels:
channel = Channel.get_channel_by_id(channel_id)
if channel:
message.tagged_channels.add(channel)
return create_success_response(message, status.HTTP_201_CREATED, many=False)
def update_message(self, data):
if data.get('modified_at'):
self.modified_at = data.get('modified_at')
if data.get('deleted_at'):
self.modified_at = data.get('deleted_at')
if data.get('content'):
content = data.get('content')
if content != self.content:
self.previous_content.append(self.content)
self.content = content
if data.get('embeds'):
embeds = data.get('embeds')
if embeds != self.embeds:
self.previous_embeds.append(self.embeds)
self.embeds = embeds
if data.get('tagged_everyone'):
tagged_everyone = data.get('tagged_everyone')
if self.tagged_everyone or tagged_everyone:
self.tagged_everyone = True
if data.get('tagged_users'):
tagged_users = data.get('tagged_users')
for user in tagged_users:
if user not in self.tagged_users:
self.tagged_users.append(user)
if data.get('tagged_roles'):
tagged_roles = data.get('tagged_roles')
for role in tagged_roles:
if role not in self.tagged_roles:
self.tagged_roles.append(role)
if data.get('tagged_channels'):
tagged_channels = data.get('tagged_channels')
for channel in tagged_channels:
if channel not in self.tagged_channels:
self.tagged_channels.append(channel)
self.save()
return create_success_response(self, status.HTTP_202_ACCEPTED, many=False)
@classmethod
def get_message_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
def __str__(self): def __str__(self):
return (f'{self.created_at} | ' return (f'{self.created_at} | '
@ -48,11 +164,113 @@ class GuildInfo(models.Model):
class AdminRequest(models.Model): class AdminRequest(models.Model):
guild = models.ForeignKey(Guild, on_delete=models.CASCADE) guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE) author = models.ForeignKey(User, on_delete=models.DO_NOTHING)
message = models.ForeignKey(Message, on_delete=models.CASCADE) message = models.ForeignKey(Message, on_delete=models.DO_NOTHING)
completed = models.BooleanField() channel = models.ForeignKey(Channel, on_delete=models.DO_NOTHING)
requested_at = models.DateTimeField() completed = models.BooleanField(default=False)
completed_at = models.DateTimeField() requested_at = models.DateTimeField(auto_now_add=True, blank=True)
completed_at = models.DateTimeField(null=True, blank=True, default=None)
completed_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, null=True, blank=True, default=None)
completed_message = models.CharField(max_length=1000, null=True, blank=True, default=None)
content = models.CharField(max_length=2000)
def update_request(self, data):
completed = data.get('completed', False)
completed_by_id = data.get('completed_by')
completed_message = data.get('message')
if not self.completed and completed:
self.completed_at = datetime.now()
self.completed_message = completed_message
user = User.get_user_by_id(completed_by_id)
if not isinstance(user, User):
return create_error_response('User Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
self.completed_by = user
self.save()
return create_request_success_response(self, status.HTTP_202_ACCEPTED)
@classmethod
def add_new_request(cls, data):
guild_id = data.get('guild')
author_id = data.get('author')
message_id = data.get('message')
channel_id = data.get('channel')
content = data.get('content')
if not (guild_id and author_id and message_id and channel_id and content):
return create_error_response("One or more of the required fields are missing.",
status=status.HTTP_400_BAD_REQUEST)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
author = User.get_author_by_id(author_id)
if not isinstance(author, User):
return create_error_response('Author Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
message = Message.get_message_by_id(message_id)
if not isinstance(message, Message):
return create_error_response('Message Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
channel = Channel.get_channel_by_id(channel_id)
if not isinstance(channel, Channel):
return create_error_response('Channel Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
request = cls(
guild=guild,
author=author,
message=message,
channel=channel,
content=content
)
request.save()
return create_request_success_response(request, status.HTTP_201_CREATED, many=False)
@classmethod
def get_request_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
def __str__(self): def __str__(self):
return f"{self.guild.id} | {self.requested_at} | By {self.author.id}" return f"{self.guild.id} | {self.requested_at} | By {self.author.id}"
class AdminComment(models.Model):
request = models.ForeignKey(AdminRequest, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.DO_NOTHING)
content = models.CharField(max_length=1000)
updated_at = models.DateTimeField(auto_now_add=True, blank=True)
@classmethod
def add_new_comment(cls, data):
request_id = data.get('request')
author_id = data.get('author')
content = data.get('content')
if not (request_id and author_id and content):
return create_error_response('Request, Author, and Content are required fields',
status=status.HTTP_400_BAD_REQUEST)
request = AdminRequest.get_request_by_id(request_id)
if not isinstance(request, AdminRequest):
return create_error_response("Admin Request Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
author = User.get_user_by_id(author_id)
if not isinstance(author, User):
return create_error_response("Author Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
comment = cls(
request=request,
author=author,
content=content
)
comment.save()
return create_comment_success_response(comment, status.HTTP_201_CREATED, many=False)
@classmethod
def get_comment_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None

View File

@ -3,6 +3,7 @@ from rest_framework import serializers
from .models import Message from .models import Message
from .models import GuildInfo from .models import GuildInfo
from .models import AdminRequest from .models import AdminRequest
from .models import AdminComment
class MessageSerializer(serializers.ModelSerializer): class MessageSerializer(serializers.ModelSerializer):
@ -10,6 +11,7 @@ class MessageSerializer(serializers.ModelSerializer):
model = Message model = Message
fields = "__all__" fields = "__all__"
class GuildInfoSerializer(serializers.ModelSerializer): class GuildInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = GuildInfo model = GuildInfo
@ -20,3 +22,9 @@ class AdminRequestSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AdminRequest model = AdminRequest
fields = "__all__" fields = "__all__"
class AdminCommentSerializer(serializers.ModelSerializer):
class Meta:
model = AdminComment
fields = "__all__"

View File

@ -0,0 +1,28 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_response(message_data, status, many: bool = False):
from .serializers import MessageSerializer
return Response(MessageSerializer(message_data, many=many).data,
status=status)
def create_request_success_response(request_data, status, many: bool = False):
from .serializers import AdminRequestSerializer
return Response(AdminRequestSerializer(request_data, many=many).data,
status=status)
def create_comment_success_response(comment_data, status, many: bool = False):
from .serializers import AdminCommentSerializer
return Response(AdminCommentSerializer(comment_data, many=many).data,
status=status)

View File

@ -1,8 +1,9 @@
from django.urls import path from django.urls import path
from .views import GuildsAPI from .views import GuildsAPI, GuildDetail
app_name = "users_api" app_name = "users_api"
urlpatterns = [ urlpatterns = [
path("/", view=GuildsAPI.as_view(), name="list") path("/", view=GuildsAPI.as_view(), name="list"),
path("/<str:id>/", view=GuildDetail.as_view(), name='detail')
] ]

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.4 on 2019-09-16 05:23 # Generated by Django 2.2.4 on 2019-09-17 19:31
import django.contrib.postgres.fields import django.contrib.postgres.fields
from django.db import migrations, models from django.db import migrations, models
@ -17,10 +17,10 @@ class Migration(migrations.Migration):
name='Guild', name='Guild',
fields=[ fields=[
('id', models.CharField(max_length=30, primary_key=True, serialize=False)), ('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
('admin_chat', models.CharField(max_length=30)), ('admin_chat', models.CharField(blank=True, max_length=30, null=True)),
('new_patron_message', models.TextField(blank=True, max_length=1000)), ('new_patron_message', models.TextField(blank=True, max_length=1000, null=True)),
('default_channel', models.CharField(max_length=30)), ('default_channel', models.CharField(max_length=30)),
('new_patron_channel', models.CharField(max_length=30)), ('new_patron_channel', models.CharField(blank=True, max_length=30, null=True)),
('prefixes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=10), size=None)), ('prefixes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=10), size=None)),
], ],
), ),

View File

@ -1,28 +0,0 @@
# Generated by Django 2.2.4 on 2019-09-17 05:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guilds', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='guild',
name='admin_chat',
field=models.CharField(blank=True, max_length=30, null=True),
),
migrations.AlterField(
model_name='guild',
name='new_patron_channel',
field=models.CharField(blank=True, max_length=30, null=True),
),
migrations.AlterField(
model_name='guild',
name='new_patron_message',
field=models.TextField(blank=True, max_length=1000, null=True),
),
]

View File

@ -1,5 +1,14 @@
import os
from django.db import models from django.db import models
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from .utils import create_error_response
from .utils import create_success_response
from .utils import create_role_success_response
# Create your models here. # Create your models here.
@ -15,11 +24,113 @@ class Guild(models.Model):
def __str__(self): def __str__(self):
return self.id return self.id
def update_guild(self, data):
if data.get('admin_chat'):
self.admin_chat = data.get('admin_chat')
if data.get('new_patron_message'):
self.new_patron_message = data.get('new_patron_message')
if data.get('default_channel'):
self.default_channel = data.get('default_channel')
if data.get('new_patron_channel'):
self.new_patron_channel = data.get('new_patron_channel')
if data.get('add_prefix'):
if data.get('add_prefix') not in self.prefixes:
self.prefixes.append(data.get('add_prefix'))
if data.get('remove_prefix'):
if data.get('remove_prefix') in self.prefixes:
self.prefixes.remove(data.get('remove_prefix'))
if len(self.prefixes) <= 0:
self.prefixes = [os.environ['DISCORD_DEFAULT_PREFIX'], ]
self.save()
return self
@classmethod
def get_guild_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
@classmethod
def create_guild(cls, data):
id = data.get('id')
default_channel = data.get('default_channel')
if not (id and default_channel):
return create_error_response('id and default_channel are required',
status=status.HTTP_400_BAD_REQUEST)
if cls.get_guild_by_id(id):
return create_error_response('That Guild already exists',
status.HTTP_409_CONFLICT)
guild = cls(
id=id,
default_channel=default_channel,
prefixes=data.get('prefixes'),
admin_chat=data.get('admin_chat'),
new_patron_message=data.get('new_patron_message'),
new_patron_channel=data.get('new_patron_channel')
)
guild.save()
return create_success_response(guild, status.HTTP_201_CREATED, many=False)
class Role(models.Model): class Role(models.Model):
id = models.CharField(max_length=30, primary_key=True) id = models.CharField(max_length=30, primary_key=True)
guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False) guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False)
type = models.PositiveSmallIntegerField() role_type = models.PositiveSmallIntegerField()
def update_role(self, data):
if data.get('role_type'):
self.role_type = data.get('role_type')
self.save()
return create_role_success_response(self, status=status.HTTP_202_ACCEPTED, many=False)
@classmethod
def add_new_role(cls, data):
id = data.get('id')
guild_id = data.get('guild')
role_type = data.get('role_type')
if not (id and guild_id and role_type):
return create_error_response("The Role ID, Guild, and Role Type are required",
status=status.HTTP_400_BAD_REQUEST)
if cls.get_role_by_id(id):
return create_error_response("That Role Already Exists",
status=status.HTTP_409_CONFLICT)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response("Guild Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
try:
role_type = int(role_type)
except ValueError:
return create_error_response("Role Type must be a positive number",
status=status.HTTP_400_BAD_REQUEST)
if role_type < 0:
return create_error_response("Role Type must be a positive number",
status=status.HTTP_400_BAD_REQUEST)
elif 1000 < role_type:
return create_error_response("Role Type must be less than 1000",
status=status.HTTP_400_BAD_REQUEST)
role = cls(
id=id,
guild=guild,
role_type=role_type
)
role.save()
return create_role_success_response(role, status.HTTP_201_CREATED, many=False)
@classmethod
def get_role_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
def __str__(self): def __str__(self):
return f"{self.guild.id} | {self.id}" return f"{self.guild.id} | {self.id}"

View File

@ -0,0 +1,8 @@
from rest_framework.permissions import BasePermission
class GuildPermissions(BasePermission):
def has_permission(self, request, view):
return super().has_permission(request, view)
def has_object_permission(self, request, view, obj):
return super().has_object_permission(request, view, obj)

View File

@ -0,0 +1,21 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_response(guild_data, status, many: bool = False):
from .serializers import GuildSerializer
return Response(GuildSerializer(guild_data, many=many).data,
status=status)
def create_role_success_response(role_data, status, many: bool = False):
from .serializers import RoleSerializer
return Response(RoleSerializer(role_data, many=many).data,
status=status)

View File

@ -1,13 +1,12 @@
import os
from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import status from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from django.core.exceptions import ObjectDoesNotExist
from geeksbot_v2.utils.api_utils import PaginatedAPIView from geeksbot_v2.utils.api_utils import PaginatedAPIView
from .models import Guild from .models import Guild
from .serializers import GuildSerializer from .utils import create_error_response
from .utils import create_success_response
# Create your views here. # Create your views here.
@ -15,37 +14,56 @@ from .serializers import GuildSerializer
class GuildsAPI(PaginatedAPIView): class GuildsAPI(PaginatedAPIView):
def get(self, request, format=None): permission_classes = [IsAuthenticated]
users = Guild.objects.all()
page = self.paginate_queryset(users)
if page is not None:
serialized_users = GuildSerializer(users, many=True)
return self.get_paginated_response(serialized_users.data)
serialized_users = GuildSerializer(users, many=True) def get(self, request, format=None):
return Response(serialized_users.data) guilds = Guild.objects.all()
page = self.paginate_queryset(guilds)
if page is not None:
return create_success_response(page, status.HTTP_200_OK, many=True)
return create_success_response(guilds, status.HTTP_200_OK, many=True)
def post(self, request, format=None): def post(self, request, format=None):
data = dict(request.data) data = dict(request.data)
print(data) return Guild.create_guild(data)
id = data.get('id')
default_channel = data.get('default_channel')
if not (id and default_channel):
return Response({'msg': 'id and default_channel are required'}, status=status.HTTP_400_BAD_REQUEST)
admin_chat = data.get('admin_chat')
new_patron_message = data.get('new_patron_message')
default_prefix = os.environ['DISCORD_DEFAULT_PREFIX']
prefixes = data.get('prefixes', [default_prefix, ])
print(prefixes)
guild = Guild( class GuildDetail(APIView):
id=id[0] if isinstance(id, list) else id, permission_classes = [IsAuthenticated]
default_channel=default_channel[0] if isinstance(default_channel, list) else default_channel,
prefixes=prefixes,
admin_chat=admin_chat[0] if isinstance(admin_chat, list) else admin_chat,
new_patron_message=new_patron_message[0] if isinstance(new_patron_message, list) else new_patron_message
)
guild.save()
return Response(GuildSerializer(guild).data, status=status.HTTP_201_CREATED) def get(self, request, id, format=None):
try:
guild = Guild.objects.get(id=id)
except ObjectDoesNotExist:
return create_error_response("Guild Does not Exist",
status=status.HTTP_404_NOT_FOUND)
else:
return create_success_response(guild,
status=status.HTTP_200_OK)
def put(self, request, id, format=None):
guild = Guild.get_guild_by_id(id)
if guild:
data = dict(request.data)
guild = guild.update_guild(data)
return create_success_response(guild,
status=status.HTTP_202_ACCEPTED)
else:
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
def delete(self, request, id, format=None):
guild = Guild.get_guild_by_id(id)
if guild:
# data = dict(request.data)
# TODO Add a check to verify user is allowed to delete...
# Possibly in object permissions...
guild.delete()
return create_success_response(guild,
status=status.HTTP_200_OK)
else:
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.4 on 2019-09-16 05:23 # Generated by Django 2.2.4 on 2019-09-17 19:31
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion

View File

@ -1,15 +1,63 @@
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from geeksbot_v2.guilds.models import Guild from geeksbot_v2.guilds.models import Guild
from geeksbot_v2.guilds.models import Role from geeksbot_v2.guilds.models import Role
from .utils import create_error_response
from .utils import create_success_creator_response
from .utils import create_success_tier_response
# Create your models here. # Create your models here.
class PatreonCreator(models.Model): class PatreonCreator(models.Model):
guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False) guilds = models.ManyToManyField(Guild)
creator = models.CharField(max_length=50, null=False) creator = models.CharField(max_length=50, null=False, primary_key=True)
link = models.CharField(max_length=100, null=False) link = models.CharField(max_length=100, null=False, unique=True)
def update_creator(self, data):
if data.get('guild'):
guild = Guild.get_guild_by_id(data.get('guild'))
if not isinstance(guild, Guild):
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
self.guilds.add(guild)
if data.get('link'):
self.link = data.get('link')
self.save()
return create_success_creator_response(self, status.HTTP_202_ACCEPTED, many=False)
@classmethod
def add_new_creator(cls, data):
creator = data.get('creator')
if PatreonCreator.get_creator_by_name(creator):
return create_error_response('That Creator already exists',
status=status.HTTP_409_CONFLICT)
link = data.get('link')
if not (creator and link):
return create_error_response('Creator and Link are both required fields',
status=status.HTTP_400_BAD_REQUEST)
guild = Guild.get_guild_by_id(data.get('guild'))
if not guild:
return create_error_response('A Valid Guild is required',
status=status.HTTP_400_BAD_REQUEST)
new_creator = cls(
creator=creator,
link=link
)
new_creator.save()
new_creator.guilds.add(guild)
return create_success_creator_response(new_creator, status.HTTP_201_CREATED, many=False)
@classmethod
def get_creator_by_name(cls, name):
try:
return cls.objects.get(creator=name)
except ObjectDoesNotExist:
return None
def __str__(self): def __str__(self):
return f"{self.guild.id} | {self.creator}" return f"{self.guild.id} | {self.creator}"
@ -17,11 +65,100 @@ class PatreonCreator(models.Model):
class PatreonTier(models.Model): class PatreonTier(models.Model):
creator = models.ForeignKey(PatreonCreator, on_delete=models.CASCADE) creator = models.ForeignKey(PatreonCreator, on_delete=models.CASCADE)
guild = models.ForeignKey(Guild, on_delete=models.CASCADE) guild = models.ManyToManyField(Guild)
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
description = models.TextField() description = models.TextField()
role = models.ForeignKey(Role, on_delete=models.CASCADE) role = models.ForeignKey(Role, on_delete=models.CASCADE)
amount = models.IntegerField(null=True) amount = models.IntegerField(null=True)
next_lower_tier = models.ForeignKey('self', null=True, blank=True)
def update_tier(self, data):
if data.get('guild'):
guild = Guild.get_guild_by_id(data.get('guild'))
if not isinstance(guild, Guild):
return create_error_response('Guild Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
self.guilds.add(guild)
if data.get('name'):
self.name = data.get('name')
if data.get('description'):
self.description = data.get('description')
if data.get('role'):
role = Role.get_role_by_id(data.get('role'))
if not isinstance(role, Role):
return create_error_response('Role Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
self.role = role
if data.get('amount'):
self.amount = data.get('amount')
if data.get('next_lower_tier'):
tier = self.get_tier_by_id(data.get('next_lower_tier'))
if not isinstance(tier, self.__class__):
return create_error_response('Next Lower Tier Does Not Exist',
status=status.HTTP_404_NOT_FOUND)
self.next_lower_tier = tier
self.save()
return create_success_tier_response(tier, status.HTTP_202_ACCEPTED, many=False)
@classmethod
def get_tier_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
@classmethod
def add_new_tier(cls, data):
creator_str = data.get('creator')
guild_id = data.get('guild')
name = data.get('name')
description = data.get('description')
role_id = data.get('role')
next_lower_tier_id = data.get('next_lower_tier')
if not (creator_str and guild_id and name and description and role_id):
return create_error_response("The Creator's name, Guild, Tier name, Description, "
"and Discord Role are all required.",
status=status.HTTP_400_BAD_REQUEST)
creator = PatreonCreator.get_creator_by_name(creator_str)
if not isinstance(creator, PatreonCreator):
return create_error_response("Creator Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response("Guild Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
role = Role.get_role_by_id(role_id)
if not isinstance(role, Role):
return create_error_response("Role Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
if next_lower_tier_id:
next_lower_tier = cls.get_tier_by_id(next_lower_tier_id)
if not isinstance(next_lower_tier, cls):
return create_error_response("Next Lower Tier Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
if next_lower_tier.guild != guild:
return create_error_response("The given next lower tier is not for the same guild.",
status=status.HTTP_400_BAD_REQUEST)
if next_lower_tier.creator != creator:
return create_error_response("The given next lower tier is not for the same creator.",
status=status.HTTP_400_BAD_REQUEST)
try:
PatreonTier.objects.filter(creator=creator, guilds__id=guild.id).get(name=name)
except ObjectDoesNotExist:
tier = cls(
creator=creator,
name=name,
description=description,
role=role,
amount=data.get('amount'),
next_lower_tier=next_lower_tier if next_lower_tier_id else None
)
tier.save()
return create_success_tier_response(tier, status.HTTP_201_CREATED, many=False)
else:
return create_error_response("A Tier with that name already exists for that creator in this guild.",
status=status.HTTP_409_CONFLICT)
def __str__(self): def __str__(self):
return f"{self.guild.id} | {self.creator.creator} | {self.name}" return f"{self.guild.id} | {self.creator.creator} | {self.name}"

View File

@ -0,0 +1,21 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_creator_response(creator_data, status, many: bool = False):
from .serializers import PatreonCreatorSerializer
return Response(PatreonCreatorSerializer(creator_data, many=many).data,
status=status)
def create_success_tier_response(tier_data, status, many: bool = False):
from .serializers import PatreonTierSerializer
return Response(PatreonTierSerializer(tier_data, many=many).data,
status=status)

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.4 on 2019-09-16 05:23 # Generated by Django 2.2.4 on 2019-09-17 19:31
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,9 +10,9 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('guilds', '0001_initial'),
('dmessages', '0001_initial'), ('dmessages', '0001_initial'),
('guilds', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [

View File

@ -1,8 +1,13 @@
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from geeksbot_v2.guilds.models import Guild from geeksbot_v2.guilds.models import Guild
from geeksbot_v2.dmessages.models import Message from geeksbot_v2.dmessages.models import Message
from geeksbot_v2.users.models import User from geeksbot_v2.users.models import User
from geeksbot_v2.channels.models import Channel
from .utils import create_error_response
from .utils import create_success_response
# Create your models here. # Create your models here.
@ -14,16 +19,110 @@ class RconServer(models.Model):
port = models.PositiveIntegerField() port = models.PositiveIntegerField()
password = models.CharField(max_length=50) password = models.CharField(max_length=50)
monitor_chat = models.BooleanField() monitor_chat = models.BooleanField()
monitor_chat_channel = models.CharField(max_length=30, blank=True) monitor_chat_channel = models.ForeignKey(
alerts_channel = models.CharField(max_length=30, blank=True) Channel, on_delete=models.DO_NOTHING, related_name="+", null=True, blank=True, default=None
info_channel = models.CharField(max_length=30, blank=True) )
alerts_channel = models.ForeignKey(
Channel, on_delete=models.DO_NOTHING, related_name="+", null=True, blank=True, default=None
)
info_channel = models.ForeignKey(
Channel, on_delete=models.DO_NOTHING, related_name="+", null=True, blank=True, default=None
)
info_message = models.ForeignKey( info_message = models.ForeignKey(
Message, on_delete=models.CASCADE, related_name="+", blank=True Message, on_delete=models.DO_NOTHING, related_name="+", null=True, blank=True, default=None
) )
settings_message = models.ForeignKey( settings_message = models.ForeignKey(
Message, on_delete=models.CASCADE, related_name="+", blank=True Message, on_delete=models.DO_NOTHING, related_name="+", null=True, blank=True, default=None
) )
whitelist = models.ManyToManyField(User, blank=True) whitelist = models.ManyToManyField(User, blank=True)
def update_server(self, data):
if data.get('name'):
self.name = data.get('name')
if data.get('ip'):
self.ip = data.get('ip')
if data.get('port'):
self.port = data.get('port')
if data.get('password'):
self.password = data.get('password')
if data.get('monitor_chat'):
self.monitor_chat = data.get('monitor_chat')
if 'monitor_chat_channel' in data.keys():
self.monitor_chat_channel = Channel.get_channel_by_id(data.get('monitor_chat_channel'))
if 'alerts_channel' in data.keys():
self.alerts_channel = Channel.get_channel_by_id(data.get('alerts_channel'))
if 'info_channel' in data.keys():
self.alerts_channel = Channel.get_channel_by_id(data.get('info_channel'))
if 'info_message' in data.keys():
self.info_message = Message.get_message_by_id(data.get('info_message'))
if 'settings_message' in data.keys():
self.settings_message = Message.get_message_by_id(data.get('settings_message'))
self.save()
return create_success_response(self, status.HTTP_202_ACCEPTED, many=False)
def add_whitelist(self, user_id):
user = User.get_user_by_id(user_id)
if not isinstance(user, User):
return create_error_response("User Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
if not user.steam_id:
return create_error_response("User does not have a Steam 64ID attached to their account",
status=status.HTTP_406_NOT_ACCEPTABLE)
self.whitelist.add(user)
return create_error_response("User has been added to the whitelist",
status=status.HTTP_200_OK)
def remove_from_whitelist(self, user_id):
user = User.get_user_by_id(user_id)
if not isinstance(user, User):
return create_error_response("User Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
self.whitelist.remove(user)
return create_error_response("User has been removed from the whitelist",
status=status.HTTP_200_OK)
@classmethod
def add_new_server(cls, data):
guild_id = data.get('guild')
name = data.get('name')
ip = data.get('ip')
port = data.get('port')
password = data.get('password')
if not (guild_id and name and ip and port and password):
return create_error_response("One or more of the required fields are missing",
status=status.HTTP_400_BAD_REQUEST)
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, Guild):
return create_error_response("Guild Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
server = cls(
guild=guild,
name=name,
ip=ip,
port=port,
password=password,
monitor_chat=data.get('monitor_chat', False)
)
server.save()
return create_success_response(server, status.HTTP_201_CREATED, many=False)
@classmethod
def get_server(cls, guild_id, name):
guild_servers = cls.get_guild_servers(guild_id)
if guild_servers:
try:
return guild_servers.get(name=name)
except ObjectDoesNotExist:
return None
return None
@classmethod
def get_guild_servers(cls, guild_id):
guild = Guild.get_guild_by_id(guild_id)
if not isinstance(guild, guild):
return None
return cls.objects.filter(guild=guild)
def __str__(self): def __str__(self):
return f"{self.guild.id} | {self.name}" return f"{self.guild.id} | {self.name}"

14
geeksbot_v2/rcon/utils.py Normal file
View File

@ -0,0 +1,14 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_response(rcon_data, status, many: bool = False):
from .serializers import RconServerSerializer
return Response(RconServerSerializer(rcon_data, many=many).data,
status=status)

View File

@ -13,7 +13,7 @@
{% if object.name %} {% if object.name %}
<p>{{ object.name }}</p> <p>{{ object.name }}</p>
{% endif %} {% endif %}
{{ user.id }} {{ user.auth_token }}
</div> </div>
</div> </div>

View File

@ -1,13 +1,17 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import admin as auth_admin from django.contrib.auth import admin as auth_admin
from .forms import UserChangeForm from .forms import UserChangeForm, UserCreateForm
from .models import User from .models import User
class UserAdmin(auth_admin.UserAdmin): class UserAdmin(auth_admin.UserAdmin):
model = User model = User
form = UserChangeForm form = UserChangeForm
add_form = UserCreateForm
add_fieldsets = auth_admin.UserAdmin.add_fieldsets + (
(None, {'fields': ('id')}),
)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View File

@ -1,8 +1,9 @@
from django.urls import path from django.urls import path
from geeksbot_v2.users.views import UsersAPI from geeksbot_v2.users.views import UsersAPI, UserDetail
app_name = "users_api" app_name = "users_api"
urlpatterns = [ urlpatterns = [
path("users/", view=UsersAPI.as_view(), name="list") path("/", view=UsersAPI.as_view(), name="list"),
path("/<str:id>/", view=UserDetail.as_view(), name="detail"),
] ]

View File

@ -1,8 +1,20 @@
from django.contrib.auth import forms from django.contrib.auth import forms
from django.forms import CharField
from allauth.account.forms import SignupForm
from .models import User from .models import User
class UserCreateForm(SignupForm):
id = CharField(max_length=30, label='Discord ID')
def save(self, request):
user = super(UserCreateForm, self).save(request)
user.id = self.cleaned_data['id']
user.save()
return user
class UserChangeForm(forms.UserChangeForm): class UserChangeForm(forms.UserChangeForm):
class Meta(forms.UserChangeForm.Meta): class Meta(forms.UserChangeForm.Meta):
model = User model = User

View File

@ -0,0 +1,70 @@
# Generated by Django 2.2.4 on 2019-09-17 19:38
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
('guilds', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
('discord_username', models.CharField(max_length=100, null=True)),
('previous_discord_usernames', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, null=True, size=None)),
('discriminator', models.IntegerField(null=True)),
('previous_discriminators', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), blank=True, null=True, size=None)),
('steam_id', models.CharField(blank=True, max_length=30, null=True)),
('animated', models.BooleanField(blank=True, null=True)),
('avatar', models.CharField(blank=True, max_length=100, null=True)),
('bot', models.BooleanField(blank=True, null=True)),
('banned', models.BooleanField(default=False)),
('logging_enabled', models.BooleanField(default=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('guilds', models.ManyToManyField(blank=True, null=True, to='guilds.Guild')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='UserLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField()),
('action', models.IntegerField()),
('description', models.CharField(max_length=100)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-09-17 21:09
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.4 on 2019-09-18 05:54
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20190917_2109'),
]
operations = [
migrations.AlterField(
model_name='user',
name='discriminator',
field=models.CharField(max_length=4, null=True),
),
migrations.AlterField(
model_name='user',
name='previous_discriminators',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=4), blank=True, null=True, size=None),
),
]

View File

View File

@ -1,4 +1,5 @@
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.db.models import CharField from django.db.models import CharField
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -8,8 +9,14 @@ from django.conf import settings
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from geeksbot_v2.guilds.models import Guild from geeksbot_v2.guilds.models import Guild
from .utils import verify_user_data
from .utils import create_error_response
from .utils import create_log_success_response
from .utils import create_success_response
@receiver(post_save, sender=settings.AUTH_USER_MODEL) @receiver(post_save, sender=settings.AUTH_USER_MODEL)
@ -23,11 +30,18 @@ class User(AbstractUser):
# First Name and Last Name do not cover name patterns # First Name and Last Name do not cover name patterns
# around the globe. # around the globe.
name = CharField(_("Name of User"), blank=True, max_length=255) name = CharField(_("Name of User"), blank=True, max_length=255)
username = models.CharField(
_('username'),
max_length=150,
unique=False,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[UnicodeUsernameValidator()],
)
id = models.CharField(max_length=30, primary_key=True) id = models.CharField(max_length=30, primary_key=True)
discord_username = models.CharField(max_length=100, null=True) discord_username = models.CharField(max_length=100, null=True)
previous_discord_usernames = ArrayField(models.CharField(max_length=100), blank=True, null=True) previous_discord_usernames = ArrayField(models.CharField(max_length=100), blank=True, null=True)
discriminator = models.IntegerField(null=True) discriminator = models.CharField(max_length=4, null=True)
previous_discriminators = ArrayField(models.IntegerField(), blank=True, null=True) previous_discriminators = ArrayField(models.CharField(max_length=4), blank=True, null=True)
guilds = models.ManyToManyField(Guild, blank=True, null=True) guilds = models.ManyToManyField(Guild, blank=True, null=True)
steam_id = models.CharField(max_length=30, blank=True, null=True) steam_id = models.CharField(max_length=30, blank=True, null=True)
animated = models.BooleanField(blank=True, null=True) animated = models.BooleanField(blank=True, null=True)
@ -36,15 +50,158 @@ class User(AbstractUser):
banned = models.BooleanField(default=False) banned = models.BooleanField(default=False)
logging_enabled = models.BooleanField(default=True) logging_enabled = models.BooleanField(default=True)
@classmethod
def add_new_user(cls, data):
if not verify_user_data(data):
return create_error_response("Not all required fields are present.",
status=status.HTTP_400_BAD_REQUEST)
id = data.get('id')
if id:
if User.objects.filter(id=id).exists():
return create_error_response("User Exists please update instead of create",
status=status.HTTP_409_CONFLICT)
discord_username = data.get('username')
discriminator = data.get('discriminator')
guild_id = data.get('guild')
try:
guild = Guild.objects.get(id=str(guild_id))
except ObjectDoesNotExist:
return create_error_response("That is not a valid Guild",
status=status.HTTP_400_BAD_REQUEST)
animated = data.get('animated')
avatar = data.get('avatar')
bot = data.get('bot')
banned = data.get('banned')
logging = data.get('logging')
if not (avatar and (animated is not None) and (bot is not None)):
return create_error_response("All required fields must contain a value",
status.HTTP_400_BAD_REQUEST)
user = User(
id=id,
discord_username=discord_username,
discriminator=discriminator,
animated=animated,
avatar=avatar,
bot=bot,
banned=banned or False,
logging_enabled=logging or True
)
user.save()
user.guilds.add(guild)
return create_success_response(user, status.HTTP_201_CREATED, many=False)
def update_user(self, data):
if data.get('username') and data.get('username') != self.discord_username:
if isinstance(self.previous_discord_usernames, list):
self.previous_discord_usernames.append(self.discord_username)
else:
self.previous_discord_usernames = [self.discord_username, ]
self.discord_username = data.get('username')
if data.get('discriminator') and data.get('discriminator') != self.discriminator:
if isinstance(self.previous_discriminators, list):
self.previous_discriminators.append(self.discriminator)
else:
self.previous_discriminators = [self.discriminator, ]
self.discriminator = data.get('discriminator')
if data.get('guild'):
guild = Guild.get_guild_by_id(data.get('guild'))
if not isinstance(guild, Guild):
return create_error_response("That is not a valid Guild",
status=status.HTTP_400_BAD_REQUEST)
self.guilds.add(guild)
if data.get('steam_id'):
self.steam_id = data.get('steam_id')
if data.get('animated'):
self.animated = data.get('animated')
if data.get('avatar'):
self.avatar = data.get('avatar')
if data.get('bot'):
self.bot = data.get('bot')
if data.get('banned'):
self.banned = data.get('banned')
if data.get('logging'):
self.logging_enabled = data.get('logging')
self.save()
return create_success_response(self, status.HTTP_202_ACCEPTED, many=False)
@classmethod
def get_user_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
def get_absolute_url(self): def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username}) return reverse("users:detail", kwargs={"username": self.username})
class UserLog(models.Model): class UserLog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
time = models.DateTimeField() time = models.DateTimeField(auto_now_add=True, blank=True)
action = models.IntegerField() action = models.IntegerField()
description = models.CharField(max_length=100) description = models.CharField(max_length=100, null=True, blank=True)
@classmethod
def add_new_log(cls, user, data):
user_id = data.get('user')
action = data.get('action')
description = data.get('description')
if not (user_id and action):
return create_error_response("User and Action are required.",
status=status.HTTP_400_BAD_REQUEST)
user = User.get_user_by_id(user_id)
if not isinstance(user, User):
return create_error_response("User Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
try:
action = int(action)
except ValueError:
return create_error_response("The Action must be a number",
status=status.HTTP_400_BAD_REQUEST)
log = cls(
user=user,
action=action,
description=description
)
log.save()
return create_log_success_response(log, status.HTTP_201_CREATED, many=False)
@classmethod
def get_log_by_id(cls, id):
try:
return cls.objects.get(id=id)
except ObjectDoesNotExist:
return None
@classmethod
def get_logs_by_user(cls, user_id, count: int = None):
user = User.get_user_by_id(user_id)
if isinstance(user, User):
user_logs = cls.objects.filter(user=user).order_by('-time')
if count:
user_logs = user_logs[:count]
if len(user_logs) > 0:
return user_logs
else:
return []
else:
return []
@classmethod
def get_logs_by_user_action(cls, user_id, action, count: int = None):
user = User.get_user_by_id(user_id)
if isinstance(user, User):
user_logs = cls.objects.filter(user=user, action=action).order_by('-time')
if count:
user_logs = user_logs[:count]
if len(user_logs) > 0:
return user_logs
else:
return []
else:
return []
def __str__(self): def __str__(self):
return f"{self.time} | {self.user.id} | {self.action}" return f"{self.time} | {self.user.id} | {self.action}"

View File

@ -7,7 +7,22 @@ from geeksbot_v2.users.models import UserLog
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = "__all__" fields = [
'id',
'username',
'name',
'discord_username',
'previous_discord_usernames',
'discriminator',
'previous_discriminators',
'guilds',
'steam_id',
'animated',
'avatar',
'bot',
'banned',
'logging_enabled'
]
class UserLogSerializer(serializers.ModelSerializer): class UserLogSerializer(serializers.ModelSerializer):

View File

@ -0,0 +1,36 @@
from rest_framework.response import Response
from rest_framework import status
def create_error_response(msg, status=status.HTTP_404_NOT_FOUND):
return Response({'details': msg},
status=status)
def create_success_response(user_data, status, many: bool = False):
from .serializers import UserSerializer
return Response(UserSerializer(user_data, many=many).data,
status=status)
def create_log_success_response(log_data, status, many: bool = False):
from .serializers import UserLogSerializer
return Response(UserLogSerializer(log_data, many=many).data,
status=status)
required_fields = [
'id',
'username',
'discriminator',
'guild',
'animated',
'avatar',
'bot',
]
def verify_user_data(data):
return all([field in data.keys() for field in required_fields])

View File

@ -1,19 +1,19 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse from django.urls import reverse
from django.views.generic import DetailView, RedirectView, UpdateView from django.views.generic import DetailView, RedirectView, UpdateView
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from geeksbot_v2.users.serializers import UserSerializer from .models import UserLog
from geeksbot_v2.users.serializers import UserLogSerializer
from geeksbot_v2.users.models import UserLog
from geeksbot_v2.utils.api_utils import PaginatedAPIView from geeksbot_v2.utils.api_utils import PaginatedAPIView
from .models import User
User = get_user_model() from .utils import create_error_response
from .utils import create_success_response
from .utils import create_log_success_response
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(LoginRequiredMixin, DetailView):
@ -24,6 +24,7 @@ class UserDetailView(LoginRequiredMixin, DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object, user=request.user) context = self.get_context_data(object=self.object, user=request.user)
return self.render_to_response(context) return self.render_to_response(context)
@ -66,44 +67,72 @@ user_redirect_view = UserRedirectView.as_view()
class UsersAPI(PaginatedAPIView): class UsersAPI(PaginatedAPIView):
def get(self, request, guild, format=None): permission_classes = [IsAuthenticated]
def get(self, request, guild=None, format=None):
if guild:
users = User.objects.filter(guilds__id=guild) users = User.objects.filter(guilds__id=guild)
else:
users = User.objects.all()
page = self.paginate_queryset(users) page = self.paginate_queryset(users)
if page is not None: if page is not None:
serialized_users = UserSerializer(users, many=True) return create_success_response(page, status.HTTP_200_OK, many=True)
return self.get_paginated_response(serialized_users.data)
serialized_users = UserSerializer(users, many=True) return create_success_response(users, status.HTTP_200_OK, many=True)
return Response(serialized_users.data)
def post(self, request, format=None):
data = dict(request.data)
return User.add_new_user(data)
class UserDetail(APIView): class UserDetail(APIView):
def get(self, request, guild, id, format=None): permission_classes = [IsAuthenticated]
user = User.objects.filter(guilds__id=guild).get(id=id)
return Response(UserSerializer(user).data) def get(self, request, id, format=None):
user = User.get_user_by_id(id)
if not isinstance(user, User):
return create_error_response("User Does not Exist",
status=status.HTTP_404_NOT_FOUND)
return create_success_response(user,
status=status.HTTP_200_OK)
def put(self, request, id, format=None):
user = User.get_user_by_id(id)
if isinstance(user, User):
data = dict(request.data)
return user.update_user(data)
else:
return create_error_response("User Does Not Exist",
status=status.HTTP_404_NOT_FOUND)
class UserLogList(PaginatedAPIView): class UserLogList(PaginatedAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, user, action=None, format=None): def get(self, request, user, action=None, format=None):
if action: if action:
user_logs = ( user_logs = UserLog.get_logs_by_user_action(user, action)
UserLog.objects.filter(user=user)
.filter(action=action)
.order_by("-time")
)
else: else:
user_logs = UserLog.objects.filter(user=user).order_by("-time") user_logs = UserLog.get_logs_by_user(user)
page = self.paginate_queryset(user_logs) page = self.paginate_queryset(user_logs)
if page is not None: if page is not None:
serialized_logs = UserLogSerializer(page, many=True) return create_log_success_response(page, status.HTTP_200_OK, many=True)
return self.get_paginated_response(serialized_logs.data)
serialized_logs = UserLogSerializer(user_logs, many=True) return create_log_success_response(user_logs, status.HTTP_200_OK, many=True)
return Response(serialized_logs.data)
def post(self, request, user, format=None):
data = dict(request.data)
return UserLog.add_new_log(user, data)
class UserLogDetail(APIView): class UserLogDetail(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, id, format=None): def get(self, request, id, format=None):
user_log = UserLog.objects.get(id=id) user_log = UserLog.get_log_by_id(id)
return Response(UserLogSerializer(user_log).data) if isinstance(user_log, UserLog):
return create_log_success_response(user_log, status.HTTP_200_OK, many=False)
else:
return create_error_response("Log Does Not Exist",
status=status.HTTP_404_NOT_FOUND)

View File

@ -61,7 +61,7 @@ listen_addresses = '*'
# defaults to 'localhost'; use '*' for all # defaults to 'localhost'; use '*' for all
# (change requires restart) # (change requires restart)
#port = 5432 # (change requires restart) #port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart) max_connections = 1000 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart) #superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories #unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart) # (change requires restart)