Added InviteCode system

This commit is contained in:
Walter 2025-02-20 22:41:07 +01:00
parent d91681a7ec
commit 3d98f297d9
8 changed files with 82 additions and 12 deletions

View File

@ -31,7 +31,7 @@ def load_insecure_key():
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") or load_insecure_key() SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") or load_insecure_key()
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") or "*" ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "*").split(" ")
# Application definition # Application definition

View File

@ -1,7 +1,8 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .models import Player, Log from .models import Player, Log, InviteCode
admin.site.register(Log) admin.site.register(Log)
admin.site.register(Player) admin.site.register(Player)
admin.site.register(InviteCode)

View File

@ -8,7 +8,7 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
from users.models import Player from users.models import Player
from users.validators import usernameValidator, username_change_validator from users.validators import username_validator, username_change_validator, invitecode_validator
from bloonsa_game.models import Config as BloonsaConfig from bloonsa_game.models import Config as BloonsaConfig
class UserRegisterForm(UserCreationForm): class UserRegisterForm(UserCreationForm):
@ -23,12 +23,20 @@ class UserRegisterForm(UserCreationForm):
self.helper.form_action = '/users/register' self.helper.form_action = '/users/register'
self.helper.add_input(Submit('submit', 'Create')) self.helper.add_input(Submit('submit', 'Create'))
invite_code = forms.CharField(max_length=64,
label="invite_code",
empty_value="code",
# help_text="",
required=True,
validators=[invitecode_validator,])
username = forms.CharField(min_length=3, username = forms.CharField(min_length=3,
max_length=16, max_length=16,
label="Username", label="Username",
required=True, required=True,
validators=[usernameValidator,], validators=[username_validator,],
help_text=_("3-16 chars, alphanumeric and _- pls")) #help_text=_("3-16 chars, alphanumeric and _- pls")
)
password1 = forms.CharField( password1 = forms.CharField(
label="Password", label="Password",

View File

@ -0,0 +1,33 @@
# Generated by Django 5.1.6 on 2025-02-20 21:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0025_alter_player_avatar'),
]
operations = [
migrations.CreateModel(
name='InviteCode',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.TextField(max_length=64)),
('code', models.TextField(max_length=64)),
('active', models.BooleanField(default=True)),
],
),
migrations.AlterField(
model_name='log',
name='action',
field=models.IntegerField(choices=[(0, 'Logged in'), (1, 'Registered'), (2, 'Logged out'), (3, 'Edited account settings'), (100, 'Loaded a level via ingame ID box'), (101, 'Loaded a level via URL'), (102, 'Loaded a random level'), (103, 'Submitted a score on level_id'), (104, 'Rated a level')]),
),
migrations.AddField(
model_name='player',
name='invite_code',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.invitecode'),
),
]

View File

@ -4,10 +4,18 @@ from django.contrib.auth.models import User
from django_resized import ResizedImageField from django_resized import ResizedImageField
class InviteCode(models.Model):
title = models.TextField(max_length=64)
code = models.TextField(max_length=64)
active = models.BooleanField(default=True)
def __str__(self):
return f"{self.title} | code: {self.code}"
class Player(models.Model): class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="player") user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="player")
# Profile # Profile
bio = models.TextField(max_length=128, null=True) bio = models.TextField(max_length=128, null=True, blank=True)
avatar = ResizedImageField(default="defaults/avatars.jpg", avatar = ResizedImageField(default="defaults/avatars.jpg",
size=[256, 256], size=[256, 256],
upload_to="avatars", upload_to="avatars",
@ -21,6 +29,7 @@ class Player(models.Model):
creation_date = models.DateTimeField(default=timezone.now) creation_date = models.DateTimeField(default=timezone.now)
latest_activity = models.DateTimeField(default=timezone.now) latest_activity = models.DateTimeField(default=timezone.now)
suspected_cheater = models.BooleanField(default=False) # This should be set when tripping the anti-cheat suspected_cheater = models.BooleanField(default=False) # This should be set when tripping the anti-cheat
invite_code = models.ForeignKey(InviteCode, null=True, blank=True, on_delete=models.CASCADE)
# States # States
suspended = models.BooleanField(default=False) # This is a shadow-ban, stats will still save but not all will show up on leaderboards suspended = models.BooleanField(default=False) # This is a shadow-ban, stats will still save but not all will show up on leaderboards
banned = models.BooleanField(default=False) # Account gets logged out upon logging in banned = models.BooleanField(default=False) # Account gets logged out upon logging in
@ -50,6 +59,8 @@ class Player(models.Model):
return f"{states}{self.user} - {self.latest_ip}".strip(" ") return f"{states}{self.user} - {self.latest_ip}".strip(" ")
class Log(models.Model): class Log(models.Model):
class Actions(models.IntegerChoices): class Actions(models.IntegerChoices):
login = 0, "Logged in" login = 0, "Logged in"
@ -68,4 +79,5 @@ class Log(models.Model):
note = models.TextField(null=True) note = models.TextField(null=True)
def __str__(self): def __str__(self):
return f"{self.player.user.username} - {self.get_action_display()} <{self.note or ''}>" return f"{self.player.user.username} - {self.get_action_display()} <{self.note or ''}>"

View File

@ -25,6 +25,9 @@ class BloonsaUtil:
if not request.user.is_authenticated: if not request.user.is_authenticated:
return return
if hasattr(request.user, "player"): if hasattr(request.user, "player"):
if not hasattr(request.user.player, "bloonsa_config"):
bloonsa_config = Config(player=request.user.player)
bloonsa_config.save()
return request.user.player return request.user.player
ip = self.get_ip(request=request) ip = self.get_ip(request=request)
player = Player(user=request.user, player = Player(user=request.user,

View File

@ -3,9 +3,10 @@ import string
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from users.models import Player from users.models import Player, InviteCode
def usernameValidator(username):
def username_validator(username):
if User.objects.filter(username__iexact=username).first() is not None: if User.objects.filter(username__iexact=username).first() is not None:
raise ValidationError("Sorry, this username is already in use") raise ValidationError("Sorry, this username is already in use")
@ -23,3 +24,10 @@ def username_change_validator(username):
charset = set(string.ascii_letters + string.digits + "_-") charset = set(string.ascii_letters + string.digits + "_-")
if not all(x in charset for x in username): if not all(x in charset for x in username):
raise ValidationError("Username may only contain normal letters, numbers and _-") raise ValidationError("Username may only contain normal letters, numbers and _-")
def invitecode_validator(invitecode):
if len(invitecode) > 64:
raise ValidationError("Invite code too long")
if not InviteCode.objects.filter(code=invitecode).exists():
raise ValidationError("Invite code doesn't exist")

View File

@ -5,7 +5,7 @@ from django.shortcuts import render, redirect
from django.views.generic import TemplateView from django.views.generic import TemplateView
from users.forms import UserRegisterForm, UserLoginForm, PlayerUpdateForm, UserUpdateForm, BloonsaConfigUpdateForm from users.forms import UserRegisterForm, UserLoginForm, PlayerUpdateForm, UserUpdateForm, BloonsaConfigUpdateForm
from users.models import Player from users.models import Player, InviteCode
from users.util import bloonsa_util, actions from users.util import bloonsa_util, actions
@ -37,9 +37,14 @@ class RegisterView(TemplateView):
form = UserRegisterForm(request.POST) form = UserRegisterForm(request.POST)
if not form.is_valid(): if not form.is_valid():
return render(request=request, template_name="users/register.html", context={"form": form}) return render(request=request, template_name="users/register.html", context={"form": form})
invite_code = InviteCode.objects.get(code=form.cleaned_data["invite_code"])
user = form.save() user = form.save()
player = bloonsa_util.init_player(request=request)
login(request=request, user=user) login(request=request, user=user)
player = bloonsa_util.init_player(request=request)
player.invite_code = invite_code
player.save()
bloonsa_util.log(player=player, bloonsa_util.log(player=player,
action=actions.login) action=actions.login)
return redirect("bloonsa_game:game") return redirect("bloonsa_game:game")