From 716f3238157f68f8ed5f67a2677a7d16f7ef5669 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 13 Aug 2020 16:41:32 -0400 Subject: [PATCH 001/190] notifications models within profiles app --- .../migrations/0012_auto_20200813_1639.py | 32 +++++++++++++++++++ profiles/models.py | 27 ++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 profiles/migrations/0012_auto_20200813_1639.py diff --git a/profiles/migrations/0012_auto_20200813_1639.py b/profiles/migrations/0012_auto_20200813_1639.py new file mode 100644 index 000000000..08aa2844e --- /dev/null +++ b/profiles/migrations/0012_auto_20200813_1639.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2.14 on 2020-08-13 20:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0011_userprofile_activisionid'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datetime', models.DateTimeField(auto_created=True)), + ('title', models.CharField(blank=True, max_length=255, null=True)), + ('description', models.TextField(default='No description given')), + ('sender', models.CharField(default='System', max_length=255)), + ('type', models.CharField(choices=[('match', 1), ('tournament', 2), ('league', 3), ('team', 4), ('support', 5), ('news', 6), ('general', 7), ('store', 8)], default='general', max_length=255)), + ('link', models.CharField(max_length=255)), + ('read', models.BooleanField(default=False)), + ('seen', models.BooleanField(default=False)), + ], + ), + migrations.AddField( + model_name='userprofile', + name='notifications', + field=models.ManyToManyField(blank=True, related_name='user_notifications', to='profiles.Notification'), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index c4bccc51a..3cf504f65 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -3,6 +3,31 @@ from django.db.models.signals import post_save from django_countries.fields import CountryField +NOTIFICATION_TYPES = [ + ('match', 1), + ('tournament', 2), + ('league', 3), + ('team', 4), + ('support', 5), + ('news', 6), + ('general', 7), + ('store', 8), +] + + +class Notification(models.Model): + title = models.CharField(max_length=255, null=True, blank=True) + description = models.TextField(default="No description given") + # set the default sender of a notification as "System" + sender = models.CharField(max_length=255, default="System") + type = models.CharField(choices=NOTIFICATION_TYPES, default='general', max_length=255) + datetime = models.DateTimeField(auto_created=True) + link = models.CharField(max_length=255) + # has the user marked the notification as read? default to false + read = models.BooleanField(default=False) + # has the user visited the notification list page since the notification was generated? used for stats + seen = models.BooleanField(default=False) + class UserProfile(models.Model): def __str__(self): @@ -12,6 +37,8 @@ def __str__(self): user = models.OneToOneField(User, related_name='user', on_delete=models.CASCADE) # xp they have from winning events xp = models.PositiveSmallIntegerField(default=0) + # all notifications associated with this user + notifications = models.ManyToManyField(Notification, related_name='user_notifications', blank=True) # credits they own from purchasing things in the store credits = models.PositiveSmallIntegerField(default=0) passes = models.PositiveSmallIntegerField(default=0) From 5d3bf3b8b67ff90efcfcf68f3f451f812b224c0e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 15 Aug 2020 07:59:43 -0400 Subject: [PATCH 002/190] notifications list template initial commit --- project-templates/profiles/notifications_list.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 project-templates/profiles/notifications_list.html diff --git a/project-templates/profiles/notifications_list.html b/project-templates/profiles/notifications_list.html new file mode 100644 index 000000000..e69de29bb From c35dab02052a67afb58a90b2d5965951c2772dc5 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 15 Aug 2020 08:29:02 -0400 Subject: [PATCH 003/190] Working notification system with marking as read and unread --- profiles/urls.py | 3 + profiles/views.py | 56 ++++++++++++++++--- .../profiles/notifications_list.html | 49 ++++++++++++++++ 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/profiles/urls.py b/profiles/urls.py index 5cc232e9b..ab8fa930f 100644 --- a/profiles/urls.py +++ b/profiles/urls.py @@ -13,4 +13,7 @@ path('users/search/', login_required(views.searchusers), name='searchusers'), path('user//', views.profile, name='profile'), path('leaderboards/', views.LeaderboardView.as_view(), name='leaderboard'), + path('notifications/', login_required(views.notifications_list), name='notifications'), + path('notifications//read/', login_required(views.notification_read), name='notification_read'), + path('notifications//unread/', login_required(views.notification_unread), name='notification_unread'), ] diff --git a/profiles/views.py b/profiles/views.py index 7778c7584..c351e5334 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -1,5 +1,6 @@ import requests from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from django.contrib import messages from django.contrib.auth import login as auth_login, REDIRECT_FIELD_NAME, logout as auth_logout, get_user_model from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm @@ -15,10 +16,10 @@ from django.utils.encoding import force_bytes, force_text from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode, is_safe_url from django.views.generic import View - +import datetime from teams.models import TeamInvite from .forms import CreateUserForm, EditProfileForm, SortForm -from .models import UserProfile +from .models import UserProfile, Notification from .tokens import account_activation_token @@ -44,7 +45,7 @@ def login(request, template_name='profiles/login_form.html', r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data) result = r.json() - + ''' End reCAPTCHA validation''' if result['success']: if not request.POST.get('remember me', None): @@ -240,6 +241,11 @@ def profile(request, urlusername): userprofile = get_object_or_404(UserProfile, user__username=urlusername) # following line is not stock olly team_list = TeamInvite.objects.filter(accepted=True, user=userprofile.user) + test = Notification(title="You visited your profile") + test.datetime = datetime.datetime.now() + test.save() + userprofile.notifications.add(test) + userprofile.save() return render(request, 'profiles/profile.html', {'userprofile': userprofile, 'requestuser': request.user, "team_list": team_list}) @@ -263,8 +269,8 @@ def searchusers(request): if query: return render(request, 'profiles/users.html', {'userprofiles': UserProfile.objects.filter - (Q(user__username__icontains=query) | Q(user__email__icontains=query) | - Q(psn__icontains=query) | Q(xbl__icontains=query))}) + (Q(user__username__icontains=query) | Q(user__email__icontains=query) | + Q(psn__icontains=query) | Q(xbl__icontains=query))}) else: return redirect('profiles:users') @@ -363,8 +369,8 @@ def post(self, request): def activate(request, uidb64, token): try: uid = force_text(urlsafe_base64_decode(uidb64)) - #a = uidb64.split("'")[1] - #uid = urlsafe_base64_decode(a).decode() + # a = uidb64.split("'")[1] + # uid = urlsafe_base64_decode(a).decode() user = User.objects.get(pk=uid) except(TypeError, ValueError, OverflowError, User.DoesNotExist) as e: print("Exception") @@ -426,3 +432,39 @@ def post(self, request, **kwargs): messages.error(request, 'No sort option selected, sorting by descending xp') return render(request, 'teams/leaderboard.html', {'user_list': user_list, 'form': self.form_class(None)}) + + +def notifications_list(request): + if request.user.is_anonymous: + messages.error('You must be signed in to view your notifications', request) + return redirect('index') + else: + profile = UserProfile.objects.get(user=request.user) + notifications = profile.notifications.all() + notifications = notifications.filter(read=False) + read = profile.notifications.all().filter(read=True) + for x in profile.notifications.all(): + x.seen = True + x.save() + return render(request, 'profiles/notifications_list.html', + {'profile': profile, 'notifications': notifications, 'read': read}) + + +def notification_read(request, pk): + try: + notif = Notification.objects.get(pk=pk) + notif.read = True + notif.save() + except ObjectDoesNotExist: + messages.error(request, 'Error marking notification as read') + return redirect('profiles:notifications') + + +def notification_unread(request, pk): + try: + notif = Notification.objects.get(pk=pk) + notif.read = False + notif.save() + except ObjectDoesNotExist: + messages.error(request, 'Error marking notification as unread') + return redirect('profiles:notifications') \ No newline at end of file diff --git a/project-templates/profiles/notifications_list.html b/project-templates/profiles/notifications_list.html index e69de29bb..a46c851ba 100644 --- a/project-templates/profiles/notifications_list.html +++ b/project-templates/profiles/notifications_list.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} + +{% block head %} + Notifications - {{ SITE_NAME }} +{% endblock %} + +{% block body %} + +

Unread Notifications

+ + + + + + + {% for notif in notifications %} + + + + + + + {% endfor %} +
TimeDescription
{{ notif.datetime }} {{ notif.title }} {{ notif.description }} + +
+
+
+

Read Notifications

+ + + + + + + {% for notif in read %} + + + + + + + {% endfor %} +
TimeDescription
{{ notif.datetime }} {{ notif.title }} {{ notif.description }} + +
+{% endblock %} \ No newline at end of file From 07844cfb3b9b2a9e644424a4b73b4943a981cd24 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 15 Aug 2020 08:33:52 -0400 Subject: [PATCH 004/190] bump version --- olly/base_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/olly/base_settings.py b/olly/base_settings.py index 925c59121..ac1a48216 100644 --- a/olly/base_settings.py +++ b/olly/base_settings.py @@ -158,4 +158,4 @@ LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/' -SITE_VERSION = "0.9.0" +SITE_VERSION = "0.9.1" From 8b24841bf5eb87b4059ff1de6c10cabae5ac656e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 15:07:49 -0400 Subject: [PATCH 005/190] match stats models --- matches/models.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/matches/models.py b/matches/models.py index 87dfacd29..e2f513a3c 100644 --- a/matches/models.py +++ b/matches/models.py @@ -5,6 +5,46 @@ from teams.models import Team +class StatsPlayer(models.Model): + rating = models.DecimalField(max_digits=6, decimal_places=3) + kills = models.IntegerField(default=0) + assists = models.IntegerField(default=0) + deaths = models.IntegerField(default=0) + killround = models.DecimalField(max_digits=6, decimal_places=3) + adr = models.IntegerField(default=0) + ud = models.IntegerField(default=0) + ef = models.IntegerField(default=0) + f_assists = models.IntegerField(default=0) + hs = models.IntegerField(default=0) + kast = models.IntegerField(default=0) + awp_k = models.IntegerField(default=0) + twok = models.IntegerField(default=0) + threek = models.IntegerField(default=0) + fourk = models.IntegerField(default=0) + fivek = models.IntegerField(default=0) + one_v_one = models.IntegerField(default=0) + one_v_two = models.IntegerField(default=0) + one_v_three = models.IntegerField(default=0) + one_v_four = models.IntegerField(default=0) + one_v_five = models.IntegerField(default=0) + f_kills = models.IntegerField(default=0) + f_deaths = models.IntegerField(default=0) + entries = models.IntegerField(default=0) + trades = models.IntegerField(default=0) + rounds = models.IntegerField(default=0) + rf = models.IntegerField(default=0) + ra = models.IntegerField(default=0) + damage = models.IntegerField(default=0) + + +class MatchStats(models.Model): + matchid = models.PositiveIntegerField(default=0) + map = models.CharField(default="unknown", max_length=255) + team1 = models.CharField(default="unknown", max_length=255) + team2 = models.CharField(default="unknown", max_length=255) + + + class SportChoice(models.Model): name = models.CharField(default='unknown sports', null=False, max_length=255) From 316686658bf6cb30f73a6ad170f2037d7f718192 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 15:08:38 -0400 Subject: [PATCH 006/190] include migrations --- .../migrations/0023_matchstats_statsplayer.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 matches/migrations/0023_matchstats_statsplayer.py diff --git a/matches/migrations/0023_matchstats_statsplayer.py b/matches/migrations/0023_matchstats_statsplayer.py new file mode 100644 index 000000000..1711e405e --- /dev/null +++ b/matches/migrations/0023_matchstats_statsplayer.py @@ -0,0 +1,58 @@ +# Generated by Django 2.2.14 on 2020-08-22 19:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0022_auto_20200410_1506'), + ] + + operations = [ + migrations.CreateModel( + name='MatchStats', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('matchid', models.PositiveIntegerField(default=0)), + ('map', models.CharField(default='unknown', max_length=255)), + ('team1', models.CharField(default='unknown', max_length=255)), + ('team2', models.CharField(default='unknown', max_length=255)), + ], + ), + migrations.CreateModel( + name='StatsPlayer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.DecimalField(decimal_places=3, max_digits=6)), + ('kills', models.IntegerField(default=0)), + ('assists', models.IntegerField(default=0)), + ('deaths', models.IntegerField(default=0)), + ('killround', models.DecimalField(decimal_places=3, max_digits=6)), + ('adr', models.IntegerField(default=0)), + ('ud', models.IntegerField(default=0)), + ('ef', models.IntegerField(default=0)), + ('f_assists', models.IntegerField(default=0)), + ('hs', models.IntegerField(default=0)), + ('kast', models.IntegerField(default=0)), + ('awp_k', models.IntegerField(default=0)), + ('twok', models.IntegerField(default=0)), + ('threek', models.IntegerField(default=0)), + ('fourk', models.IntegerField(default=0)), + ('fivek', models.IntegerField(default=0)), + ('one_v_one', models.IntegerField(default=0)), + ('one_v_two', models.IntegerField(default=0)), + ('one_v_three', models.IntegerField(default=0)), + ('one_v_four', models.IntegerField(default=0)), + ('one_v_five', models.IntegerField(default=0)), + ('f_kills', models.IntegerField(default=0)), + ('f_deaths', models.IntegerField(default=0)), + ('entries', models.IntegerField(default=0)), + ('trades', models.IntegerField(default=0)), + ('rounds', models.IntegerField(default=0)), + ('rf', models.IntegerField(default=0)), + ('ra', models.IntegerField(default=0)), + ('damage', models.IntegerField(default=0)), + ], + ), + ] From 97bb47f977ebc00a0a5509942d16a60975cd6b80 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:25:24 -0400 Subject: [PATCH 007/190] dont create a teaminvite when creating a team --- teams/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teams/views.py b/teams/views.py index 26e33c616..dedeefcf2 100644 --- a/teams/views.py +++ b/teams/views.py @@ -188,7 +188,7 @@ def post(self, request): Team.founder = self.request.user Team.save() - invite = TeamInvite() + """invite = TeamInvite() invite.expire = timezone.now() invite.user = self.request.user invite.captain = 'founder' @@ -197,7 +197,7 @@ def post(self, request): invite.inviter = self.request.user invite.inviter_id = self.request.user.id invite.team_id = Team.id - invite.save() + invite.save()""" messages.success(self.request, 'Your Team has been created successfully') return redirect('teams:list') From ab128c34567c776e24e05b02f73c8a88b2e97918 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:54:01 -0400 Subject: [PATCH 008/190] attempt to send a notification to the receiving user about invite --- teams/views.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/teams/views.py b/teams/views.py index dedeefcf2..248ccf077 100644 --- a/teams/views.py +++ b/teams/views.py @@ -243,6 +243,15 @@ def post(self, request): if form.data['captain'] == 'captain' or form.data['captain'] == 'founder': TeamInvite.hasPerms = True TeamInvite.save() + # lets send a notification + notif = UserProfile.objects.get(user=TeamInvite.user) + temp = Notification(type='team', title="You've been invited to join a team", + description="What are you waiting for? Someone needs you to join their team! " + "View your team invites now!", link='teams:myinvitelist') + temp.save() + notif = notif.add(temp) + notif.save() + messages.success(request, 'Successfully notified user') if invitee.email_enabled: current_site = get_current_site(request) mail_subject = settings.SITE_NAME + ": You've been invited to a team!" From 064b475c1c5ddc69b32300d4e8595b2fac1287ed Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:54:11 -0400 Subject: [PATCH 009/190] fix hard coded links --- teams/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/teams/views.py b/teams/views.py index 248ccf077..696877a97 100644 --- a/teams/views.py +++ b/teams/views.py @@ -232,7 +232,7 @@ def post(self, request): query = invite.filter(user=invitee.user, team=form.data['team']) if query.exists(): messages.error(request, "That user already has been invited to this team") - return redirect('/teams/') + return redirect('teams:list') else: TeamInvite = form.instance TeamInvite.inviter = self.request.user @@ -266,11 +266,11 @@ def post(self, request): ) email.send() messages.success(request, 'Sent invite successfully') - return redirect('/teams/') + return redirect('teams:list') else: messages.error(request, "You must be a captain or the founder to invite") - return redirect('/teams/') + return redirect('teams:list') class LeaveTeamView(View): From f79f22135fc458cd6aebafae5c3a0dfc243cdd64 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:56:35 -0400 Subject: [PATCH 010/190] remove captain membership, modify captain and player mtm field --- teams/models.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/teams/models.py b/teams/models.py index a0c65dfaf..231bca2b7 100644 --- a/teams/models.py +++ b/teams/models.py @@ -21,9 +21,9 @@ class Team(models.Model): # whoever filled out the form to create the team, limited to only one founder = models.ForeignKey(User, related_name='founder', on_delete=models.SET_NULL, null=True) # basically founder permissions, but to other people that didn't create the actual team - captain = models.ManyToManyField(User, through='CaptainMembership', related_name='teamcaptain') + captains = models.ManyToManyField(User, blank=True, related_name='team_captains') # the people of the actual team, now a many to many, not a forkey - players = models.ManyToManyField(User, through='TeamInvite', through_fields=('team', 'user', 'inviter')) + players = models.ManyToManyField(User, blank=True, related_name='team_players') # when they created the team created = models.DateTimeField(auto_now_add=True) # when they last updated anything in the team @@ -88,9 +88,3 @@ class TeamInvite(models.Model): def __str__(self): return str(self.user) - - -class CaptainMembership(models.Model): - user = models.ForeignKey(User, related_name='captainperson', on_delete=models.CASCADE) - team = models.ForeignKey(Team, related_name='team', on_delete=models.CASCADE) - active = models.BooleanField(default=True) From 126bfb49bb6c520e5180419f8f0a860dfb665257 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:56:52 -0400 Subject: [PATCH 011/190] import notification, and switch queries to Q --- teams/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/teams/views.py b/teams/views.py index 696877a97..b4571ad89 100644 --- a/teams/views.py +++ b/teams/views.py @@ -11,9 +11,10 @@ from django.urls import reverse from django.utils import timezone from django.views.generic import ListView, DetailView, View +from django.db.models import Q from matches.models import Match -from profiles.models import UserProfile +from profiles.models import UserProfile, Notification # team create forms from teams.forms import TeamCreateForm # import the team models @@ -31,7 +32,9 @@ class MyInvitesListView(ListView): model = TeamInvite def get(self, request): - teaminvite_list = TeamInvite.objects.filter(user=self.request.user, active=True) + + teaminvite_list = TeamInvite.objects.filter(Q(user=self.request.user, active=True)) + #teaminvite_list = teaminvite_list.filter(Q(fou)) return render(request, 'teams/team_invite_list.html', {'teaminvite_list': teaminvite_list}) def get_queryset(self): From e45813c954d5f4369fd3a6b7ed8cd4a1a36a1e2c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 17:00:28 -0400 Subject: [PATCH 012/190] had to manually modify, fixed now --- teams/migrations/0009_auto_20200822_1657.py | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 teams/migrations/0009_auto_20200822_1657.py diff --git a/teams/migrations/0009_auto_20200822_1657.py b/teams/migrations/0009_auto_20200822_1657.py new file mode 100644 index 000000000..76865f4ed --- /dev/null +++ b/teams/migrations/0009_auto_20200822_1657.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.14 on 2020-08-22 20:57 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teams', '0008_team_image'), + ] + + operations = [ + migrations.RemoveField( + model_name='team', + name='captain', + ), + migrations.AddField( + model_name='team', + name='captains', + field=models.ManyToManyField(blank=True, related_name='team_captains', to=settings.AUTH_USER_MODEL), + ), + migrations.RemoveField( + model_name='team', + name='players', + field=models.ManyToManyField(blank=True, related_name='team_players', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='team', + name='players', + field=models.ManyToManyField(blank=True, related_name='team_players', to=settings.AUTH_USER_MODEL), + ), + migrations.DeleteModel( + name='CaptainMembership', + ), + ] From b00bf591ec4cfc98c9df4f1380b5a4ec72111352 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 17:19:54 -0400 Subject: [PATCH 013/190] TODO: change captain to account for multiple captains --- project-templates/staff/teams/team_detail.html | 12 ++++++++++-- project-templates/teams/team_detail.html | 4 ++++ staff/views/teams.py | 8 ++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/project-templates/staff/teams/team_detail.html b/project-templates/staff/teams/team_detail.html index 9391db668..cbd7f4c1d 100644 --- a/project-templates/staff/teams/team_detail.html +++ b/project-templates/staff/teams/team_detail.html @@ -30,6 +30,11 @@ Founder {{ team.founder }} + + Captain + {{ team.captain.name }} + + Created {{ team.created }} @@ -67,8 +72,11 @@ - Remove users from team + Remove + users from team
- Delete Team + Delete Team {% endblock %} diff --git a/project-templates/teams/team_detail.html b/project-templates/teams/team_detail.html index dd4dd96ef..56e4e7b47 100644 --- a/project-templates/teams/team_detail.html +++ b/project-templates/teams/team_detail.html @@ -40,6 +40,10 @@ Founder {{ team.founder }} + + Captain + {{ team.captain.name }} + Created {{ team.created }} diff --git a/staff/views/teams.py b/staff/views/teams.py index f7b703c9e..ddd84acbd 100644 --- a/staff/views/teams.py +++ b/staff/views/teams.py @@ -23,8 +23,11 @@ def teams_detail(request, pk): return render(request, 'staff/permissiondenied.html') else: team = Team.objects.get(id=pk) - players = TeamInvite.objects.filter(team=team, accepted=True).order_by('id') - return render(request, 'staff/teams/team_detail.html', {'team': team, 'players': players, 'pk': pk}) + players = team.players.all() + captain = team.captain.all() + + return render(request, 'staff/teams/team_detail.html', + {'team': team, 'players': players, 'captain': captain, 'pk': pk}) def create_team(request): @@ -61,6 +64,7 @@ def delete_team(request, pk): return redirect('staff:teamindex') +#TODO remove TeamInvite object def remove_user(request, pk): user = UserProfile.objects.get(user__username=request.user.username) allowed = ['superadmin', 'admin'] From 8ba9deaf6fc08e9bc1e98c8d5eb3add5af21d1ce Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 26 Aug 2020 18:17:07 -0400 Subject: [PATCH 014/190] remove comment --- teams/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/teams/forms.py b/teams/forms.py index e01f033cd..67ac6174f 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -54,7 +54,6 @@ class TeamInviteFormPost(forms.ModelForm): class Meta: captain = forms.BooleanField(required=False) model = TeamInvite - # maybe???? fields = ('user', 'team', 'captain',) widgets = { 'user': forms.CharField(), From 74679f9f0101a6507beb1af90151c0fb9e5a17ce Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 26 Aug 2020 18:17:36 -0400 Subject: [PATCH 015/190] pep8 --- teams/views.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/teams/views.py b/teams/views.py index b4571ad89..716770179 100644 --- a/teams/views.py +++ b/teams/views.py @@ -32,9 +32,8 @@ class MyInvitesListView(ListView): model = TeamInvite def get(self, request): - teaminvite_list = TeamInvite.objects.filter(Q(user=self.request.user, active=True)) - #teaminvite_list = teaminvite_list.filter(Q(fou)) + # teaminvite_list = teaminvite_list.filter(Q(fou)) return render(request, 'teams/team_invite_list.html', {'teaminvite_list': teaminvite_list}) def get_queryset(self): @@ -68,7 +67,7 @@ def invite_view(request, num): invite.expire = timezone.now() invite.active = False invite.save() - messages.success(request, 'Accepted invite to '+str(invite.team.name)) + messages.success(request, 'Accepted invite to ' + str(invite.team.name)) return redirect('/teams/') elif accepted == 'off': invite = TeamInvite.objects.get(id=num) @@ -76,7 +75,7 @@ def invite_view(request, num): invite.expire = timezone.now() invite.active = False invite.save() - messages.success(request, 'Declined invite to '+str(invite.team.name)) + messages.success(request, 'Declined invite to ' + str(invite.team.name)) return redirect('/teams/') @@ -101,13 +100,13 @@ def edit_team_view(request, pk): teamobj = get_object_or_404(Team, id=pk) form = EditTeamProfileForm(request.POST, request.FILES, instance=teamobj) if form.is_valid(): - #teamobj.about_us = form.data['about_us'] - #teamobj.website = form.data['website'] - #teamobj.twitter = form.data['twitter'] - #teamobj.twitch = form.data['twitch'] - #teamobj.country = form.data['country'] - #teamobj.image = form.data['image'] - #teamobj.save() + # teamobj.about_us = form.data['about_us'] + # teamobj.website = form.data['website'] + # teamobj.twitter = form.data['twitter'] + # teamobj.twitch = form.data['twitch'] + # teamobj.country = form.data['country'] + # teamobj.image = form.data['image'] + # teamobj.save() form.save() messages.success(request, 'Team successfully updated') return redirect(reverse('teams:detail', args=[pk])) @@ -142,9 +141,11 @@ def get(self, request, pk): messages.warning(request, "Xbox Live is not verified") if not user.psn_verified: messages.warning(request, "PSN is not verified") - return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'up':up,'pk': pk, 'matches': matches}) + return render(request, 'teams/team_detail.html', + {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) else: - return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'up':up, 'pk': pk, 'matches': matches}) + return render(request, 'teams/team_detail.html', + {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) def get_context_date(self, **kwargs): context = super(MyTeamDetailView, self).get_context_date(**kwargs) From 188e9713908feb4605234778a692f9a897539b16 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 26 Aug 2020 18:17:51 -0400 Subject: [PATCH 016/190] rewrite team create and team leave --- teams/views.py | 59 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/teams/views.py b/teams/views.py index 716770179..4db1bf40e 100644 --- a/teams/views.py +++ b/teams/views.py @@ -12,6 +12,7 @@ from django.utils import timezone from django.views.generic import ListView, DetailView, View from django.db.models import Q +from django.core.exceptions import ObjectDoesNotExist from matches.models import Match from profiles.models import UserProfile, Notification @@ -204,7 +205,7 @@ def post(self, request): invite.save()""" messages.success(self.request, 'Your Team has been created successfully') - return redirect('teams:list') + return redirect('teams:detail', pk=Team.pk) def get_invites(form): @@ -223,11 +224,11 @@ def post(self, request): form = TeamInviteFormPost(request.POST) team = Team.objects.get(id=form.data['team']) invite = get_invites(form) - captains = invite.filter(captain='captain') + captains = Team.captains.all() x = {} - for captain in captains: - x[captain] = str(captain.user.username) - if (request.user == team.founder) or (request.user.username in x.values()): + # for captain in captains: + # x[captain] = str(captain.user.username) + if (request.user == team.founder) or (request.user.username in captains): try: invitee = UserProfile.objects.get(user__username=form.data['user']) except: @@ -235,16 +236,15 @@ def post(self, request): return render(request, 'teams/team_invite_player.html', {'form': form}) query = invite.filter(user=invitee.user, team=form.data['team']) if query.exists(): - messages.error(request, "That user already has been invited to this team") - return redirect('teams:list') + messages.error(request, "That user already has already been invited to this team") + return redirect('teams:detail', pk=team.pk) else: TeamInvite = form.instance TeamInvite.inviter = self.request.user TeamInvite.team = team TeamInvite.user = invitee.user TeamInvite.expire = timezone.now() + datetime.timedelta(days=1) - TeamInvite.captain = form.data['captain'] - if form.data['captain'] == 'captain' or form.data['captain'] == 'founder': + if form.data['captain']: TeamInvite.hasPerms = True TeamInvite.save() # lets send a notification @@ -288,23 +288,30 @@ def get(self, request, pk): def post(self, request, pk): form = self.form_class(request.POST) - try: - if form.data['confirmed']: - invite = TeamInvite.objects.get(user=request.user, team_id=pk) - try: - invite.delete() - messages.success(request, "Left team") - invites = TeamInvite.objects.filter(team_id=pk) - if not invites.exists(): - team = Team.objects.get(id=pk) - team.delete() - messages.success(request, 'Deleted team due to the last user leaving') - return redirect('teams:list') - except: - messages.error(request, "You don't appear to be on this team") - return redirect('teams:detail', pk=pk) - except: - messages.error(request, "You submitted without confirming that you wanted to leave, redirecting to team detail") + if form.data['confirmed']: + try: + team = Team.objects.get(pk=pk) + except ObjectDoesNotExist: + messages.error(request, 'Team cannot be found') + return redirect('teams:list') + if request.user in team.players: + team.players.remove(request.user) + messages.success(request, 'Successfully removed you from the players role') + if request.user in team.captains: + team.captains.remove(request.user) + messages.success(request, 'Successfully removed you from the captain role') + if request.user is team.founder: + # founders cannot leave their team. they must delete the team + messages.error(request, + 'You cannot leave the team you founded, you can only delete it.') + if not (request.user in team.players) or not (request.user in team.captains) or not ( + request.user is team.founder): + messages.error(request, "You don't appear to be on this team") + return redirect('teams:detail', pk=pk) + return redirect('teams:list') + else: + messages.error(request, + "You submitted without confirming that you wanted to leave, redirecting to team detail") return redirect('teams:detail', pk=pk) From 5d5d26d1c65546c6aa299e1eb79b39732cdf6d55 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 26 Aug 2020 18:18:07 -0400 Subject: [PATCH 017/190] simplify teaminvite model, make captain a simple bool field --- teams/models.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/teams/models.py b/teams/models.py index 231bca2b7..a987a2047 100644 --- a/teams/models.py +++ b/teams/models.py @@ -72,19 +72,14 @@ def __str__(self): class TeamInvite(models.Model): - INVITE_CHOICES = ( - ('captain', 'Captain'), - ('player', 'Player'), - ) expire = models.DateTimeField(auto_now=False, auto_now_add=False) team = models.ForeignKey(Team, related_name='invitedto', on_delete=models.CASCADE) user = models.ForeignKey(User, related_name='toinvite', on_delete=models.CASCADE) inviter = models.ForeignKey(User, related_name='frominvite', on_delete=models.CASCADE) - captain = models.CharField(choices=INVITE_CHOICES, max_length=20, default='player') + captain = models.BooleanField(default=False) accepted = models.BooleanField(default=False) declined = models.BooleanField(default=False) active = models.BooleanField(default=True) - hasPerms = models.BooleanField(default=False) def __str__(self): return str(self.user) From c989daad3eba97fd0d8b41b970f7d48d6e9ad283 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 26 Aug 2020 18:18:24 -0400 Subject: [PATCH 018/190] migrations for models changes --- teams/migrations/0010_auto_20200826_1818.py | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 teams/migrations/0010_auto_20200826_1818.py diff --git a/teams/migrations/0010_auto_20200826_1818.py b/teams/migrations/0010_auto_20200826_1818.py new file mode 100644 index 000000000..d0e67b1c3 --- /dev/null +++ b/teams/migrations/0010_auto_20200826_1818.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.15 on 2020-08-26 22:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0009_auto_20200822_1657'), + ] + + operations = [ + migrations.RemoveField( + model_name='teaminvite', + name='hasPerms', + ), + migrations.AlterField( + model_name='teaminvite', + name='captain', + field=models.BooleanField(default=False), + ), + ] From 266ce2e0eb500b3cb367069cade0089f8789158c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 13:34:45 -0400 Subject: [PATCH 019/190] show all captains on team detail page in staff and front end --- project-templates/staff/teams/team_detail.html | 6 ++++-- project-templates/teams/team_detail.html | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/project-templates/staff/teams/team_detail.html b/project-templates/staff/teams/team_detail.html index cbd7f4c1d..73815a083 100644 --- a/project-templates/staff/teams/team_detail.html +++ b/project-templates/staff/teams/team_detail.html @@ -31,8 +31,10 @@ {{ team.founder }} - Captain - {{ team.captain.name }} + Captains + {% for captain in captains %} + {{ captain.name }} + {% endfor %} diff --git a/project-templates/teams/team_detail.html b/project-templates/teams/team_detail.html index 56e4e7b47..03e4fc084 100644 --- a/project-templates/teams/team_detail.html +++ b/project-templates/teams/team_detail.html @@ -41,8 +41,10 @@ {{ team.founder }} - Captain - {{ team.captain.name }} + Captains + {% for captain in captains %} + {{ captain.name }} + {% endfor %} Created @@ -73,7 +75,7 @@ Players - {% for profile in up %} + {% for profile in players %} {% endfor %} - + {% if players.count == 0 %} + + + There are no players on the team yet + + + {% endif %} Leave team
From a8c7164c12a8ce9326f4fdf963bb1ec4b4572241 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:18:19 -0400 Subject: [PATCH 020/190] update team invite list template --- project-templates/teams/team_invite_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project-templates/teams/team_invite_list.html b/project-templates/teams/team_invite_list.html index 2fe95cdb0..8e38ab05c 100644 --- a/project-templates/teams/team_invite_list.html +++ b/project-templates/teams/team_invite_list.html @@ -12,7 +12,7 @@ Invite ID From Team Name - Team Role + Captain Role Active? From e6754f8c14e13101a4caae9090f4b13f8168cf4c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:18:49 -0400 Subject: [PATCH 021/190] update team forms to not use teaminvite objects --- teams/forms.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/teams/forms.py b/teams/forms.py index 67ac6174f..f81f6a97b 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -1,7 +1,8 @@ from django import forms - +from django.db.models import Q # import the actual team model for the create team forms from teams.models import Team +from profiles.models import UserProfile # import the model for the team invite from teams.models import TeamInvite @@ -42,8 +43,10 @@ def __init__(self, request, *args, **kwargs): self.fields['captain'].widget.attrs.update({'name': 'captain', 'class': 'form-control', 'style': 'width:30%'}) self.username = request.user - invites = TeamInvite.objects.filter(hasPerms=True, user=request.user, accepted=True) - teams = Team.objects.filter(id__in=invites.values_list('team')) + # invites = TeamInvite.objects.filter(user=request.user, accepted=True) + # profile = UserProfile.objects.get(user=request.user) + # teams = Team.objects.filter(founder=request.user) + teams = Team.objects.filter(Q(captains__exact=request.user) | Q(founder=request.user)) # super().__init__(*args, **kwargs) self.fields['team'].queryset = teams @@ -102,7 +105,7 @@ class RemoveUserForm(forms.Form): def __init__(self, request, pk, *args, **kwargs): team = Team.objects.get(id=pk) - players = TeamInvite.objects.filter(team=team, accepted=True) + players = team.players.all() super().__init__(*args, **kwargs) self.fields['remove'].queryset = players self.fields['remove'].widget.attrs.update( From a5585a38a34cf5f4594cf71c839c5e319af0609f Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:19:03 -0400 Subject: [PATCH 022/190] pass captains to staff team detail template --- staff/views/teams.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/staff/views/teams.py b/staff/views/teams.py index ddd84acbd..7c4403b69 100644 --- a/staff/views/teams.py +++ b/staff/views/teams.py @@ -24,10 +24,10 @@ def teams_detail(request, pk): else: team = Team.objects.get(id=pk) players = team.players.all() - captain = team.captain.all() + captains = team.captain.all() return render(request, 'staff/teams/team_detail.html', - {'team': team, 'players': players, 'captain': captain, 'pk': pk}) + {'team': team, 'players': players, 'captains': captains, 'pk': pk}) def create_team(request): From 6d31f14691d4cb8aac860291af8771e6974fd10e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:19:55 -0400 Subject: [PATCH 023/190] pass captains to team detail template --- teams/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teams/views.py b/teams/views.py index 4db1bf40e..937ff7c0f 100644 --- a/teams/views.py +++ b/teams/views.py @@ -143,10 +143,10 @@ def get(self, request, pk): if not user.psn_verified: messages.warning(request, "PSN is not verified") return render(request, 'teams/team_detail.html', - {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) + {'team': team, 'players': players, 'pk': pk, 'matches': matches, 'captains': captains}) else: return render(request, 'teams/team_detail.html', - {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) + {'team': team, 'players': players, 'pk': pk, 'matches': matches, 'captains': captains}) def get_context_date(self, **kwargs): context = super(MyTeamDetailView, self).get_context_date(**kwargs) From e8e1c68ed5c24729dd805786e67b5456f1529c1d Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:20:19 -0400 Subject: [PATCH 024/190] remove lots of teaminvite object usage - simplify --- teams/views.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/teams/views.py b/teams/views.py index 937ff7c0f..63a9a699b 100644 --- a/teams/views.py +++ b/teams/views.py @@ -68,16 +68,18 @@ def invite_view(request, num): invite.expire = timezone.now() invite.active = False invite.save() + team = invite.team + if invite.captain: + team.captains.add(request.user) + else: + team.players.add(request.user) messages.success(request, 'Accepted invite to ' + str(invite.team.name)) - return redirect('/teams/') + return redirect('teams:list') elif accepted == 'off': invite = TeamInvite.objects.get(id=num) - invite.declined = True - invite.expire = timezone.now() - invite.active = False - invite.save() + invite.delete() messages.success(request, 'Declined invite to ' + str(invite.team.name)) - return redirect('/teams/') + return redirect('teams:list') class MyTeamsListView(ListView): @@ -129,10 +131,8 @@ class MyTeamDetailView(DetailView): def get(self, request, pk): team = get_object_or_404(Team, id=pk) - players = TeamInvite.objects.filter(team=team, accepted=True) - up = [] - for player in players: - up.append(UserProfile.objects.get(user__username=player)) + players = team.players.all() + captains = team.captains.all() matches_ = Match.objects.filter(awayteam_id=team.id) matches__ = Match.objects.filter(hometeam_id=team.id) matches = matches_ | matches__ @@ -172,7 +172,8 @@ def form_valid(self, form): def get_queryset(self): # TO DO switch the filter to the players field not just the founder field. - return Team.objects.filter(founder=self.request.user) + # TODO: FIX + return Team.objects.filter(Q(founder=self.request.user) or Q(captains__in=self.request.user)) class TeamCreateView(View): @@ -224,7 +225,7 @@ def post(self, request): form = TeamInviteFormPost(request.POST) team = Team.objects.get(id=form.data['team']) invite = get_invites(form) - captains = Team.captains.all() + captains = team.captains.all() x = {} # for captain in captains: # x[captain] = str(captain.user.username) @@ -245,13 +246,14 @@ def post(self, request): TeamInvite.user = invitee.user TeamInvite.expire = timezone.now() + datetime.timedelta(days=1) if form.data['captain']: - TeamInvite.hasPerms = True + TeamInvite.captain = True TeamInvite.save() # lets send a notification notif = UserProfile.objects.get(user=TeamInvite.user) temp = Notification(type='team', title="You've been invited to join a team", description="What are you waiting for? Someone needs you to join their team! " "View your team invites now!", link='teams:myinvitelist') + temp.datetime = datetime.datetime.utcnow() temp.save() notif = notif.add(temp) notif.save() @@ -320,7 +322,7 @@ class RemoveUserView(View): def get(self, request, pk): team = Team.objects.get(id=pk) - if request.user == team.founder: + if request.user == team.founder or request.user in team.captains: form = RemoveUserForm(request, pk) return render(request, 'teams/team_remove_user.html', {'form': form, 'pk': pk}) else: @@ -329,9 +331,11 @@ def get(self, request, pk): def post(self, request, pk): team = Team.objects.get(id=pk) - if request.user == team.founder: + if request.user == team.founder or request.user in team.captains: form = RemovePlayerFormPost(request.POST) invite = TeamInvite.objects.get(id=form.data['remove']) + player = UserProfile.objects.get() + messages.success(request, 'Removed user %s from team' % invite) invite.delete() invites = TeamInvite.objects.filter(team=team) From a9b41a1125c6ad47d7d57d3204a698a0ef831202 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:51:21 -0400 Subject: [PATCH 025/190] rewrite team create report to not use teaminvite objects --- matches/views.py | 210 ++++++++++++++++++++++++----------------------- 1 file changed, 107 insertions(+), 103 deletions(-) diff --git a/matches/views.py b/matches/views.py index afff4b6ab..66a55a30c 100644 --- a/matches/views.py +++ b/matches/views.py @@ -77,13 +77,15 @@ def post(self, request, pk): if not match.bye_2 and not match.bye_1: team1 = Team.objects.get(id=match.hometeam_id) team2 = Team.objects.get(id=match.awayteam_id) - team1_reporters = TeamInvite.objects.filter(team=team1, hasPerms=True) - team2_reporters = TeamInvite.objects.filter(team=team2, hasPerms=True) - - if TeamInvite.objects.filter(user=self.request.user, team=team1).exists(): - reporter_team = TeamInvite.objects.get(user=self.request.user, team=team1) - elif TeamInvite.objects.filter(user=self.request.user, team=team2).exists(): - reporter_team = TeamInvite.objects.get(user=self.request.user, team=team2) + team1_reporters = team1.captains + team2_reporters = team2.captains + + one_perms = False + two_perms = False + if self.request.user in team1.captains or self.request.user == team1.founder: + one_perms = True + elif self.request.user in team2.captains or self.request.user == team2.founder: + two_perms = True else: messages.error(request, message="You aren't a part of the teams in this match") if match.type == 'w': @@ -91,114 +93,116 @@ def post(self, request, pk): return redirect('matches:detail', pk=pk) if MatchReport.objects.filter(match=match.id, - reporting_team=team1).exists() and reporter_team.id == team1.id: + reporting_team=team1).exists() and one_perms: messages.error(request, "Your team has already reported this match") if match.type == 'w': return redirect('wagers:list') return redirect('matches:detail', pk=pk) elif MatchReport.objects.filter(match=match.id, - reporting_team=team2).exists() and reporter_team.id == team2.id: + reporting_team=team2).exists() and two_perms: messages.error(request, "Your team has already reported this match") if match.type == 'w': return redirect('wagers:list') return redirect('matches:detail', pk=pk) + elif match.bye_1: + messages.error(request, 'There is only one team in this match, reporting is unnecessary') + return redirect('matches:list') + + elif match.bye_2: + messages.error(request, 'There are no teams in this match') + return redirect('matches:list') else: - if reporter_team in team1_reporters or reporter_team in team2_reporters: - report.match = match - report.reporting_team = reporter_team.team - reported_team = Team.objects.get(id=form.data['reported_winner']) - report.reported_winner = reported_team - if reporter_team.team == team1: - match.team1reported = True - match.team1reportedwinner = report.reported_winner - match.team1reportedwinner_id = report.reported_winner.id - elif reporter_team.team == team2: - match.team2reported = True - match.team2reportedwinner = report.reported_winner - match.team2reportedwinner_id = report.reported_winner.id - else: - messages.error(self.request, "Something went wrong (this shouldn't be seen)") - return redirect('singletournaments:list') - match.save() - report.save() + # if reporter_team in team1_reporters or reporter_team in team2_reporters: + report.match = match + reported_team = Team.objects.get(id=form.data['reported_winner']) + report.reported_winner = reported_team + if one_perms: + report.reporting_team = team1 + match.team1reported = True + match.team1reportedwinner = report.reported_winner + match.team1reportedwinner_id = report.reported_winner.id + + elif two_perms: + report.reporting_team = team2 + match.team2reported = True + match.team2reportedwinner = report.reported_winner + match.team2reportedwinner_id = report.reported_winner.id + else: + messages.error(request, "ERROR: Could not verify the team that you're on") + return redirect('matches:detail', pk=match.id) + + match.save() + report.save() + if match.team1reported and match.team2reported: + reports = MatchReport.objects.filter(match_id=match.id) + report1 = MatchReport.objects.get(reporting_team=team1, match_id=match.id) + report2 = MatchReport.objects.get(reporting_team=team2, match_id=match.id) + if reports[0].reported_winner != reports[1].reported_winner: + dispute = MatchDispute(id=match.id, match=match, team1=team1, team2=team2, + team1origreporter=report1.reporting_user, + team2origreporter=report2.reporting_user) + dispute.save() + match.disputed = True + match.save() + + for i in [report1.reporting_user, report2.reporting_user]: + if i.user.email_enabled: + current_site = get_current_site(request) + mail_subject = settings.SITE_NAME + ' match disputed!' + message = render_to_string('matches/dispute_email.html', { + 'user': i.username, + 'site': settings.SITE_NAME, + 'domain': current_site.domain, + 'pk': dispute.pk + }) + to_email = i.email + email = EmailMessage( + mail_subject, message, from_email=settings.FROM_EMAIL, to=[to_email] + ) + email.send() + + messages.warning(self.request, + "Both teams have reported different winners; a dispute has been created") + return redirect('matches:dispute', pk=dispute.pk) + if match.team1reported: + # team 1 reported + if match.team1reportedwinner == team2: + # team1 is reporting that team2 won + # declare team2 as winner + match.winner = team2 + match.loser = team1 + match.save() + elif match.team1reportedwinner == team1: + # have to wait for the other team to confirm + pass + elif match.team2reported: + if match.team2reportedwinner == team1: + # team 1 wins + match.winner = team1 + match.loser = team2 + match.save() + elif match.team2reportedwinner == team2: + pass if match.team1reported and match.team2reported: - reports = MatchReport.objects.filter(match_id=match.id) - report1 = MatchReport.objects.get(reporting_team=team1, match_id=match.id) - report2 = MatchReport.objects.get(reporting_team=team2, match_id=match.id) - if reports[0].reported_winner != reports[1].reported_winner: - dispute = MatchDispute(id=match.id, match=match, team1=team1, team2=team2, - team1origreporter=report1.reporting_user, - team2origreporter=report2.reporting_user) - dispute.save() - match.disputed = True + if match.team2reportedwinner == team2 and match.team1reportedwinner == team2: + match.winner = team2 + match.loser = team1 match.save() - - for i in [report1.reporting_user, report2.reporting_user]: - if i.user.email_enabled: - current_site = get_current_site(request) - mail_subject = settings.SITE_NAME + ' match disputed!' - message = render_to_string('matches/dispute_email.html', { - 'user': i.username, - 'site': settings.SITE_NAME, - 'domain': current_site.domain, - 'pk': dispute.pk - }) - to_email = i.email - email = EmailMessage( - mail_subject, message, from_email=settings.FROM_EMAIL, to=[to_email] - ) - email.send() - - messages.warning(self.request, - "Both teams have reported different winners; a dispute has been created") - return redirect('matches:dispute', pk=dispute.pk) - if match.team1reported: - # team 1 reported - if match.team1reportedwinner == team2: - # team1 is reporting that team2 won - # declare team2 as winner - match.winner = team2 - match.loser = team1 - match.save() - elif match.team1reportedwinner == team1: - # have to wait for the other team to confirm - pass - elif match.team2reported: - if match.team2reportedwinner == team1: - # team 1 wins - match.winner = team1 - match.loser = team2 - match.save() - elif match.team2reportedwinner == team2: - pass - if match.team1reported and match.team2reported: - if match.team2reportedwinner == team2 and match.team1reportedwinner == team2: - match.winner = team2 - match.loser = team1 - match.save() - elif match.team2reportedwinner == team1 and match.team1reportedwinner == team1: - match.winner = team1 - match.loser = team2 - match.save() - # self.success_url = reverse('matches:detail', args=[match.id]) - messages.success(self.request, 'Your Report has been successfully submitted') - if match.type == 'w': - return redirect('wagers:list') - return redirect('matches:detail', pk=pk) - else: - messages.error(self.request, "You don't have permissions to report on this match") - if match.type == 'w': - return redirect('wagers:list') - return redirect('singletournaments:list') - # else: - # messages.error(request, "A report has already been created for this match") - # return redirect('matches:detail', pk=pk) - elif match.bye_1: - messages.error(request, 'There is only one team in this match, reporting is unnecessary') - return redirect('matches:list') - elif match.bye_2: - messages.error(request, 'There are no teams in this match') - return redirect('matches:list') + elif match.team2reportedwinner == team1 and match.team1reportedwinner == team1: + match.winner = team1 + match.loser = team2 + match.save() + # self.success_url = reverse('matches:detail', args=[match.id]) + messages.success(self.request, 'Your Report has been successfully submitted') + if match.type == 'w': + return redirect('wagers:list') + return redirect('matches:detail', pk=pk) + # if match.type == 'w': + # return redirect('wagers:list') + # return redirect('singletournaments:list') + # else: + # messages.error(request, "A report has already been created for this match") + # return redirect('matches:detail', pk=pk) class MatchDisputeReportCreateView(CreateView): From 0cf1c2b397a2e19ea276555c24132bebe5d12161 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:51:39 -0400 Subject: [PATCH 026/190] further work on removing teaminvite objects --- teams/views.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/teams/views.py b/teams/views.py index 63a9a699b..f3e780f8b 100644 --- a/teams/views.py +++ b/teams/views.py @@ -226,7 +226,7 @@ def post(self, request): team = Team.objects.get(id=form.data['team']) invite = get_invites(form) captains = team.captains.all() - x = {} + # x = {} # for captain in captains: # x[captain] = str(captain.user.username) if (request.user == team.founder) or (request.user.username in captains): @@ -333,20 +333,18 @@ def post(self, request, pk): team = Team.objects.get(id=pk) if request.user == team.founder or request.user in team.captains: form = RemovePlayerFormPost(request.POST) - invite = TeamInvite.objects.get(id=form.data['remove']) - player = UserProfile.objects.get() - - messages.success(request, 'Removed user %s from team' % invite) - invite.delete() - invites = TeamInvite.objects.filter(team=team) - if not invites.exists(): - messages.warning(request, "Last user in team removed, team deleted") - team.delete() + # invite = TeamInvite.objects.get(id=form.data['remove']) + player = UserProfile.objects.get(form.data['remove']) + if player == team.founder: + messages.error(request, "You cannot remove the Team founder from the team") return redirect('teams:list') else: - return redirect('teams:detail', pk) + team.players.remove(player) + team.save() + messages.success(request, 'Removed user %s from team' % player) + else: - messages.error(request, "Only the team's founder can remove users") + messages.error(request, "Only the team's founder/captain can remove users") return redirect('teams:detail', pk) @@ -369,9 +367,12 @@ def post(self, request, pk): if form.is_valid(): if form.cleaned_data['confirmed']: team = Team.objects.get(id=pk) - invites = list(TeamInvite.objects.filter(team=team)) - for invite in invites: - invite.delete() + try: + invites = list(TeamInvite.objects.filter(team=team)) + for invite in invites: + invite.delete() + except: + messages.error(request, "Warning: Couldn't delete team invites") messages.success(request, 'Dissolved team %s' % team) team.delete() return redirect('teams:list') @@ -379,5 +380,5 @@ def post(self, request, pk): messages.warning(request, "You didn't confirm that you wanted to dissolve the team") return redirect('teams:detail', pk) else: - messages.error(request, "Only the team's founder can remove users") + messages.error(request, "Only the team's founder can dissolve the team") return redirect('teams:detail', pk) From 0592d0a415cbc2baf161d75460e808bf699b7925 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 14:55:11 -0400 Subject: [PATCH 027/190] update match list to not use teaminvites --- matches/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matches/views.py b/matches/views.py index 66a55a30c..74ac12c99 100644 --- a/matches/views.py +++ b/matches/views.py @@ -6,7 +6,7 @@ from django.shortcuts import render, redirect from django.template.loader import render_to_string from django.views.generic import DetailView, CreateView, View - +from django.db.models import Q from matches.models import Match, MatchReport, MatchDispute, MapPoolChoice from teams.models import Team, TeamInvite from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm @@ -23,10 +23,10 @@ def get(self, request, **kwargs): class MatchList(View): def get(self, request): - invites = TeamInvite.objects.filter(hasPerms=True, user_id=request.user.id) - team = list(Team.objects.filter(id__in=invites.values_list('team', flat=True))) - matches_away = Match.objects.filter(awayteam__in=team) - matches_home = Match.objects.filter(hometeam__in=team) + teams = Team.objects.filter( + Q(captains__exact=request.user) | Q(founder=request.user) | Q(players__exact=request.user)) + matches_away = Match.objects.filter(awayteam__in=teams) + matches_home = Match.objects.filter(hometeam__in=teams) matches = matches_away | matches_home return render(request, 'matches/matches_list.html', {'matches': matches}) From 30ee812c78c36bd3ebd60475a6820a07ee0e8017 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 15:01:48 -0400 Subject: [PATCH 028/190] fix myteam list and edit team permissions --- teams/views.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/teams/views.py b/teams/views.py index f3e780f8b..a06ac7199 100644 --- a/teams/views.py +++ b/teams/views.py @@ -88,20 +88,25 @@ class MyTeamsListView(ListView): model = Team def get(self, request): - team_list = TeamInvite.objects.filter(user=self.request.user, accepted=True) + team_list = Team.objects.filter( + Q(captains__exact=request.user) | Q(founder=request.user) | Q(players__exact=request.user)) + # team_list = TeamInvite.objects.filter(user=self.request.user, accepted=True) return render(request, 'teams/team_list.html', {'team_list': team_list}) def get_queryset(self, **kwargs): - # TO DO switch the filter to the players field not just the founder field. - if TeamInvite.objects.filter(user=self.request.user, accepted=True): - # TO DO switch the filter to the players field not just the founder field. - return TeamInvite.objects.filter(user=self.request.user, accepted=True) + return Team.objects.filter( + Q(captains__exact=self.request.user) | Q(founder=self.request.user) | Q(players__exact=self.request.user)) def edit_team_view(request, pk): if request.method == 'POST': teamobj = get_object_or_404(Team, id=pk) form = EditTeamProfileForm(request.POST, request.FILES, instance=teamobj) + if request.user in teamobj.captains or request.user is teamobj.founder: + pass + else: + messages.error(request, 'ERROR: You must be a captain or founder update team info') + return redirect('teams:detail', pk=teamobj.pk) if form.is_valid(): # teamobj.about_us = form.data['about_us'] # teamobj.website = form.data['website'] @@ -139,9 +144,11 @@ def get(self, request, pk): if not request.user.is_anonymous: user = UserProfile.objects.get(user__username=request.user.username) if not user.xbl_verified: - messages.warning(request, "Xbox Live is not verified") + pass + # messages.warning(request, "Xbox Live is not verified") if not user.psn_verified: - messages.warning(request, "PSN is not verified") + pass + # messages.warning(request, "PSN is not verified") return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'pk': pk, 'matches': matches, 'captains': captains}) else: @@ -173,7 +180,8 @@ def form_valid(self, form): def get_queryset(self): # TO DO switch the filter to the players field not just the founder field. # TODO: FIX - return Team.objects.filter(Q(founder=self.request.user) or Q(captains__in=self.request.user)) + return Team.objects.filter( + Q(captains__exact=self.request.user) | Q(founder=self.request.user) | Q(players__exact=self.request.user)) class TeamCreateView(View): From ef0c05352ca016c8af22158fb2afa66ce7d880c6 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:25:24 -0400 Subject: [PATCH 029/190] Revert "dont create a teaminvite when creating a team" This reverts commit 97bb47f977ebc00a0a5509942d16a60975cd6b80. --- teams/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teams/views.py b/teams/views.py index dedeefcf2..26e33c616 100644 --- a/teams/views.py +++ b/teams/views.py @@ -188,7 +188,7 @@ def post(self, request): Team.founder = self.request.user Team.save() - """invite = TeamInvite() + invite = TeamInvite() invite.expire = timezone.now() invite.user = self.request.user invite.captain = 'founder' @@ -197,7 +197,7 @@ def post(self, request): invite.inviter = self.request.user invite.inviter_id = self.request.user.id invite.team_id = Team.id - invite.save()""" + invite.save() messages.success(self.request, 'Your Team has been created successfully') return redirect('teams:list') From dd3efd6d44cb59b51ba1fbb9a5ab4f53cc918099 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 22 Aug 2020 16:25:24 -0400 Subject: [PATCH 030/190] Revert "Revert "dont create a teaminvite when creating a team"" This reverts commit ef0c05352ca016c8af22158fb2afa66ce7d880c6. --- teams/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teams/views.py b/teams/views.py index 26e33c616..dedeefcf2 100644 --- a/teams/views.py +++ b/teams/views.py @@ -188,7 +188,7 @@ def post(self, request): Team.founder = self.request.user Team.save() - invite = TeamInvite() + """invite = TeamInvite() invite.expire = timezone.now() invite.user = self.request.user invite.captain = 'founder' @@ -197,7 +197,7 @@ def post(self, request): invite.inviter = self.request.user invite.inviter_id = self.request.user.id invite.team_id = Team.id - invite.save() + invite.save()""" messages.success(self.request, 'Your Team has been created successfully') return redirect('teams:list') From b55159d1049206077a94b52a58be455e0575d205 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 18:34:35 -0400 Subject: [PATCH 031/190] requested changes --- teams/views.py | 38 +++----------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/teams/views.py b/teams/views.py index a06ac7199..fe570f3f3 100644 --- a/teams/views.py +++ b/teams/views.py @@ -34,7 +34,6 @@ class MyInvitesListView(ListView): def get(self, request): teaminvite_list = TeamInvite.objects.filter(Q(user=self.request.user, active=True)) - # teaminvite_list = teaminvite_list.filter(Q(fou)) return render(request, 'teams/team_invite_list.html', {'teaminvite_list': teaminvite_list}) def get_queryset(self): @@ -90,7 +89,6 @@ class MyTeamsListView(ListView): def get(self, request): team_list = Team.objects.filter( Q(captains__exact=request.user) | Q(founder=request.user) | Q(players__exact=request.user)) - # team_list = TeamInvite.objects.filter(user=self.request.user, accepted=True) return render(request, 'teams/team_list.html', {'team_list': team_list}) def get_queryset(self, **kwargs): @@ -102,19 +100,10 @@ def edit_team_view(request, pk): if request.method == 'POST': teamobj = get_object_or_404(Team, id=pk) form = EditTeamProfileForm(request.POST, request.FILES, instance=teamobj) - if request.user in teamobj.captains or request.user is teamobj.founder: - pass - else: + if request.user not in teamobj.captains or request.user is not teamobj.founder: messages.error(request, 'ERROR: You must be a captain or founder update team info') return redirect('teams:detail', pk=teamobj.pk) if form.is_valid(): - # teamobj.about_us = form.data['about_us'] - # teamobj.website = form.data['website'] - # teamobj.twitter = form.data['twitter'] - # teamobj.twitch = form.data['twitch'] - # teamobj.country = form.data['country'] - # teamobj.image = form.data['image'] - # teamobj.save() form.save() messages.success(request, 'Team successfully updated') return redirect(reverse('teams:detail', args=[pk])) @@ -143,12 +132,6 @@ def get(self, request, pk): matches = matches_ | matches__ if not request.user.is_anonymous: user = UserProfile.objects.get(user__username=request.user.username) - if not user.xbl_verified: - pass - # messages.warning(request, "Xbox Live is not verified") - if not user.psn_verified: - pass - # messages.warning(request, "PSN is not verified") return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'pk': pk, 'matches': matches, 'captains': captains}) else: @@ -178,8 +161,6 @@ def form_valid(self, form): invite.team = self.request.team def get_queryset(self): - # TO DO switch the filter to the players field not just the founder field. - # TODO: FIX return Team.objects.filter( Q(captains__exact=self.request.user) | Q(founder=self.request.user) | Q(players__exact=self.request.user)) @@ -202,16 +183,6 @@ def post(self, request): Team.founder = self.request.user Team.save() - """invite = TeamInvite() - invite.expire = timezone.now() - invite.user = self.request.user - invite.captain = 'founder' - invite.hasPerms = True - invite.accepted = True - invite.inviter = self.request.user - invite.inviter_id = self.request.user.id - invite.team_id = Team.id - invite.save()""" messages.success(self.request, 'Your Team has been created successfully') return redirect('teams:detail', pk=Team.pk) @@ -234,9 +205,6 @@ def post(self, request): team = Team.objects.get(id=form.data['team']) invite = get_invites(form) captains = team.captains.all() - # x = {} - # for captain in captains: - # x[captain] = str(captain.user.username) if (request.user == team.founder) or (request.user.username in captains): try: invitee = UserProfile.objects.get(user__username=form.data['user']) @@ -245,7 +213,7 @@ def post(self, request): return render(request, 'teams/team_invite_player.html', {'form': form}) query = invite.filter(user=invitee.user, team=form.data['team']) if query.exists(): - messages.error(request, "That user already has already been invited to this team") + messages.error(request, "That user has already been invited to this team") return redirect('teams:detail', pk=team.pk) else: TeamInvite = form.instance @@ -352,7 +320,7 @@ def post(self, request, pk): messages.success(request, 'Removed user %s from team' % player) else: - messages.error(request, "Only the team's founder/captain can remove users") + messages.error(request, "Only the team's founder or a captain can remove users") return redirect('teams:detail', pk) From 78af01c812f035e6202b600a0391d34ee5d742a9 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 18:48:01 -0400 Subject: [PATCH 032/190] add a pk1 field for specifying the object to redirect the user to --- profiles/migrations/0013_notification_pk1.py | 18 ++++++++++++++++++ profiles/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 profiles/migrations/0013_notification_pk1.py diff --git a/profiles/migrations/0013_notification_pk1.py b/profiles/migrations/0013_notification_pk1.py new file mode 100644 index 000000000..4a0e47a6b --- /dev/null +++ b/profiles/migrations/0013_notification_pk1.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-08-27 22:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0012_auto_20200813_1639'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='pk1', + field=models.IntegerField(default=0), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 3cf504f65..05f78d4b1 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -23,6 +23,7 @@ class Notification(models.Model): type = models.CharField(choices=NOTIFICATION_TYPES, default='general', max_length=255) datetime = models.DateTimeField(auto_created=True) link = models.CharField(max_length=255) + pk1 = models.IntegerField(default=0) # has the user marked the notification as read? default to false read = models.BooleanField(default=False) # has the user visited the notification list page since the notification was generated? used for stats From 70fd8c1cafe8decfd7d0cfa7a6f3257ee28e1442 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 18:55:47 -0400 Subject: [PATCH 033/190] fix team list template --- project-templates/teams/team_list.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project-templates/teams/team_list.html b/project-templates/teams/team_list.html index f5b9f3474..907ca3a72 100644 --- a/project-templates/teams/team_list.html +++ b/project-templates/teams/team_list.html @@ -14,12 +14,12 @@ Date Created - {% for TeamInvite in team_list %} + {% for team in team_list %} - {{ TeamInvite.team.id }} - {{ TeamInvite.team.name }} - {{ TeamInvite.team.created }} + {{ team.id }} + {{ team.name }} + {{ team.created }} {% endfor %} From 34069b179549224558c91dbb1ddf3ee33402de4e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 18:56:14 -0400 Subject: [PATCH 034/190] remove test notification --- profiles/views.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/profiles/views.py b/profiles/views.py index c351e5334..8339b6ecc 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -241,11 +241,6 @@ def profile(request, urlusername): userprofile = get_object_or_404(UserProfile, user__username=urlusername) # following line is not stock olly team_list = TeamInvite.objects.filter(accepted=True, user=userprofile.user) - test = Notification(title="You visited your profile") - test.datetime = datetime.datetime.now() - test.save() - userprofile.notifications.add(test) - userprofile.save() return render(request, 'profiles/profile.html', {'userprofile': userprofile, 'requestuser': request.user, "team_list": team_list}) From 6d0395817a8739045f64e3723edd20aa005af81e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 18:56:40 -0400 Subject: [PATCH 035/190] send the user to the specific object if the notification is aware --- project-templates/profiles/notifications_list.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project-templates/profiles/notifications_list.html b/project-templates/profiles/notifications_list.html index a46c851ba..0fba0c308 100644 --- a/project-templates/profiles/notifications_list.html +++ b/project-templates/profiles/notifications_list.html @@ -16,7 +16,11 @@

Unread Notifications

{% for notif in notifications %} {{ notif.datetime }} - {{ notif.title }} + {% if notif.pk1 is not 0 %} + {{ notif.title }} + {% else %} + {{ notif.title }} + {% endif %} {{ notif.description }}

Back to Round

diff --git a/staff/urls.py b/staff/urls.py index 384ab6ab6..2685fff7e 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -77,6 +77,7 @@ path('matches/disputed/', login_required(views.disputed_matches), name='disputed_matches'), path('match/', login_required(views.match_detail), name='match_detail'), path('match//declare', login_required(views.MatchDeclareWinner.as_view()), name='match_declare_winner'), + path('match//dispute', login_required(views.set_dispute_match), name='match_create_dispute'), path('match//delete', login_required(views.match_delete_winner), name='match_delete_winner'), path('match//edit', login_required(views.match_edit), name='match_edit'), path('round//edit', login_required(views.edit_round), name='edit_round'), diff --git a/staff/views/matches.py b/staff/views/matches.py index 9f306b7fb..e1e63880c 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -1,10 +1,15 @@ from django.contrib import messages from django.shortcuts import render, redirect from django.views.generic import View - +from django.template.loader import render_to_string +from django.conf import settings from matches.models import MatchReport, MatchDispute from staff.forms import * from wagers.models import * +from profiles.models import UserProfile, Notification +from django.core.mail import EmailMessage +import datetime +from django.contrib.sites.shortcuts import get_current_site def matches_index(request): @@ -481,3 +486,41 @@ def create_map_pool_choice(request): else: form = GameChoiceForm(request.POST, request.FILES) return render(request, 'staff/matches/editmappool.html', {'form': form}) + + +def set_dispute_match(request, pk): + # set the specific match as disputed + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + match = Match.objects.get(pk=pk) + match.disputed = True + for i in [match.team1.players, match.team2.players]: + temp = Notification(title="A staff member set one of your matches as disputed") + temp.link = 'matches:detail' + temp.pk1 = match.pk + temp.datetime = datetime.datetime.now() + temp.save() + userprofile = UserProfile.objects.get(user=i.user) + userprofile.notifications.add(temp) + userprofile.save() + if i.user.email_enabled: + current_site = get_current_site(request) + mail_subject = settings.SITE_NAME + ' match disputed!' + message = render_to_string('matches/dispute_email.html', { + 'user': i.username, + 'site': settings.SITE_NAME, + 'domain': current_site.domain, + 'pk': match.pk + }) + to_email = i.email + email = EmailMessage( + mail_subject, message, from_email=settings.FROM_EMAIL, to=[to_email] + ) + email.send() + dispute = MatchDispute(id=match.id, match=match, team1=match.team1, team2=match.team2) + dispute.save() + messages.success(request, "Set the match as disputed, notified users, and created the Match Dispute") + return redirect('staff:match_detail', pk=match.id) diff --git a/teams/views.py b/teams/views.py index fe570f3f3..d2e672e73 100644 --- a/teams/views.py +++ b/teams/views.py @@ -229,6 +229,7 @@ def post(self, request): temp = Notification(type='team', title="You've been invited to join a team", description="What are you waiting for? Someone needs you to join their team! " "View your team invites now!", link='teams:myinvitelist') + temp.datetime = datetime.datetime.utcnow() temp.save() notif = notif.add(temp) From ebff51e9a638dd5ece91cb8e24efa89fc8ee68ae Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 19:12:50 -0400 Subject: [PATCH 039/190] fix notification add error --- teams/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teams/views.py b/teams/views.py index d2e672e73..9e1f555a1 100644 --- a/teams/views.py +++ b/teams/views.py @@ -232,7 +232,7 @@ def post(self, request): temp.datetime = datetime.datetime.utcnow() temp.save() - notif = notif.add(temp) + notif = notif.notifications.add(temp) notif.save() messages.success(request, 'Successfully notified user') if invitee.email_enabled: From b27e06da610b7402f0a362a711d9abdec70110be Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 27 Aug 2020 19:18:16 -0400 Subject: [PATCH 040/190] idiot --- teams/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teams/views.py b/teams/views.py index 9e1f555a1..ee9f8fb57 100644 --- a/teams/views.py +++ b/teams/views.py @@ -232,7 +232,7 @@ def post(self, request): temp.datetime = datetime.datetime.utcnow() temp.save() - notif = notif.notifications.add(temp) + notif.notifications.add(temp) notif.save() messages.success(request, 'Successfully notified user') if invitee.email_enabled: From b45775dc9edf4b573701a60040deed4f1434b4dd Mon Sep 17 00:00:00 2001 From: cmr6689 Date: Thu, 27 Aug 2020 19:58:17 -0400 Subject: [PATCH 041/190] fixed #104 and added teams to admin page --- teams/admin.py | 4 ++++ teams/views.py | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/teams/admin.py b/teams/admin.py index 8c38f3f3d..4d9e613e4 100644 --- a/teams/admin.py +++ b/teams/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from .models import Team, TeamInvite + # Register your models here. +admin.site.register(Team) +admin.site.register(TeamInvite) \ No newline at end of file diff --git a/teams/views.py b/teams/views.py index ee9f8fb57..f3739a3cd 100644 --- a/teams/views.py +++ b/teams/views.py @@ -3,7 +3,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.sites.shortcuts import get_current_site -from django.core.mail import EmailMessage +from django.core.mail import EmailMessage, send_mail from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.shortcuts import render, redirect @@ -221,10 +221,14 @@ def post(self, request): TeamInvite.team = team TeamInvite.user = invitee.user TeamInvite.expire = timezone.now() + datetime.timedelta(days=1) - if form.data['captain']: - TeamInvite.captain = True - TeamInvite.save() + #TODO remove try except + try: + if form.data['captain']: + TeamInvite.captain = True + except: + pass # lets send a notification + TeamInvite.save() notif = UserProfile.objects.get(user=TeamInvite.user) temp = Notification(type='team', title="You've been invited to join a team", description="What are you waiting for? Someone needs you to join their team! " @@ -247,7 +251,7 @@ def post(self, request): email = EmailMessage( mail_subject, message, from_email=settings.FROM_EMAIL, to=[invitee.user.email] ) - email.send() + email.send(fail_silently=True) messages.success(request, 'Sent invite successfully') return redirect('teams:list') From 864e8782490015a20ba76cd96bd4e5b1a46bce81 Mon Sep 17 00:00:00 2001 From: cmr6689 Date: Thu, 27 Aug 2020 20:02:49 -0400 Subject: [PATCH 042/190] fixed #104 in matches --- matches/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matches/views.py b/matches/views.py index fe032b38b..c60127f23 100644 --- a/matches/views.py +++ b/matches/views.py @@ -169,7 +169,7 @@ def post(self, request, pk): email = EmailMessage( mail_subject, message, from_email=settings.FROM_EMAIL, to=[to_email] ) - email.send() + email.send(fail_silently=True) messages.warning(self.request, "Both teams have reported different winners; a dispute has been created") From 358518db69a7132999d607582242b1ac9759b849 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 1 Sep 2020 19:08:43 -0400 Subject: [PATCH 043/190] add steam64 and discord field, closes #106 --- profiles/forms.py | 2 ++ .../migrations/0014_auto_20200901_1908.py | 23 +++++++++++++++++++ profiles/models.py | 2 ++ project-templates/profiles/edit_profile.html | 6 +++++ 4 files changed, 33 insertions(+) create mode 100644 profiles/migrations/0014_auto_20200901_1908.py diff --git a/profiles/forms.py b/profiles/forms.py index 3cb4e1d1f..d60e4910e 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -32,6 +32,8 @@ class Meta: 'xbl', 'psn', 'steam', + 'steamid64', + 'discord', 'epic', 'lol', 'battlenet', diff --git a/profiles/migrations/0014_auto_20200901_1908.py b/profiles/migrations/0014_auto_20200901_1908.py new file mode 100644 index 000000000..378882db4 --- /dev/null +++ b/profiles/migrations/0014_auto_20200901_1908.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.15 on 2020-09-01 23:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0013_notification_pk1'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='discord', + field=models.CharField(blank=True, default='No Dsicord', max_length=255), + ), + migrations.AddField( + model_name='userprofile', + name='steamid64', + field=models.CharField(blank=True, default='No SteamID64', max_length=255), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 05f78d4b1..e1588b4da 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -47,6 +47,8 @@ def __str__(self): total_earning = models.PositiveSmallIntegerField(default=0) current_earning = models.PositiveSmallIntegerField(default=0) about_me = models.TextField(default='Forever a mystery', blank=True) + steamid64 = models.CharField(max_length=255, default='No SteamID64', blank=True) + discord = models.CharField(max_length=255, default='No Dsicord', blank=True) xbl = models.CharField(max_length=30, default='No Xbox Live Linked', blank=True) psn = models.CharField(max_length=30, default='No PSN Linked', blank=True) steam = models.CharField(max_length=30, default='No Steam Linked', blank=True) diff --git a/project-templates/profiles/edit_profile.html b/project-templates/profiles/edit_profile.html index e5aa00098..cc7ca36f5 100644 --- a/project-templates/profiles/edit_profile.html +++ b/project-templates/profiles/edit_profile.html @@ -22,6 +22,12 @@
{{ form.psn }}
+ +
{{ form.steamid64 }}
+ + +
{{ form.discord }}
+
{{ form.activisionid }}
From 63c0b57015c4b52fde0d9616de146286ff06d748 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 1 Sep 2020 19:47:51 -0400 Subject: [PATCH 044/190] update changelog --- changelog.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/changelog.md b/changelog.md index 1fbb15c38..af54d7050 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,23 @@ Project Olly Changelog + +# 1.0.0 +- Add SteamID64 and Discord profile fields +- Add basic stats models (will be built upon further in later updates) +- Added notification system for users +- Rework team invites and team roles entirely (from the backend) +- Fix multiple team list templates to work with new standards +- Improve staff panel teams templates +- Remove some hard coded links +- Add free agents to Leagues +- Allow staff to disable free agent registration for leagues within LeagueSettings +- Implement point system for LeagueMatches +- Basis team checkin process ahead of matches +- Multiple other staff panel league improvements +- Front end league template improvements (standings, and more) + +# 0.9.0 +- Implement leagues functionality + # 0.8.1 - News articles publish date now auto fills with last saved date when editing - Fix news post fields (fixes a possible 500 error when editing). closes #54 From 67fea9f40b0f1ce808394b7a8d7cf234ffd975e8 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 1 Sep 2020 20:37:58 -0400 Subject: [PATCH 045/190] fix error on staff team detail page --- staff/views/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staff/views/teams.py b/staff/views/teams.py index 7c4403b69..b11ef2d04 100644 --- a/staff/views/teams.py +++ b/staff/views/teams.py @@ -24,7 +24,7 @@ def teams_detail(request, pk): else: team = Team.objects.get(id=pk) players = team.players.all() - captains = team.captain.all() + captains = team.captains.all() return render(request, 'staff/teams/team_detail.html', {'team': team, 'players': players, 'captains': captains, 'pk': pk}) From 22154da2b0b2411c8d916fba23dc29d19ce2efa7 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 17:35:15 -0400 Subject: [PATCH 046/190] begin adding FA model to leagues models --- leagues/migrations/0017_auto_20200901_1921.py | 38 +++++++++++++++++++ leagues/models.py | 10 +++++ 2 files changed, 48 insertions(+) create mode 100644 leagues/migrations/0017_auto_20200901_1921.py diff --git a/leagues/migrations/0017_auto_20200901_1921.py b/leagues/migrations/0017_auto_20200901_1921.py new file mode 100644 index 000000000..96772a7d5 --- /dev/null +++ b/leagues/migrations/0017_auto_20200901_1921.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.15 on 2020-09-01 23:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0014_auto_20200901_1908'), + ('leagues', '0016_league_teams'), + ] + + operations = [ + migrations.AddField( + model_name='leaguesettings', + name='allow_fa', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='leagueteam', + name='points', + field=models.PositiveIntegerField(default=0), + ), + migrations.CreateModel( + name='LeagueFreeAgent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(default='Include information about Free Agent here')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fa_profile', to='profiles.UserProfile')), + ], + ), + migrations.AddField( + model_name='league', + name='fa', + field=models.ManyToManyField(blank=True, related_name='league_fas', to='leagues.LeagueFreeAgent'), + ), + ] diff --git a/leagues/models.py b/leagues/models.py index ae44f42f2..d6efed60b 100644 --- a/leagues/models.py +++ b/leagues/models.py @@ -3,6 +3,7 @@ from matches.models import Match, GameChoice, PlatformChoice, MapPoolChoice, MapChoice, SportChoice from matches.settings import TEAMFORMAT_CHOICES, MAPFORMAT_CHOICES from singletournaments.models import SingleTournamentRuleset +from profiles.models import UserProfile # a way to create default values for a field over multiple seasons @@ -40,6 +41,8 @@ class LeagueSettings(models.Model): num_divisions = models.PositiveSmallIntegerField(default=2) # max amount of teams to allow into a division max_division_size = models.PositiveSmallIntegerField(default=5) + # whether or not to allow users to register as a free agent to the league + allow_fa = models.BooleanField(default=False) def __str__(self): return self.name @@ -52,6 +55,7 @@ class LeagueTeam(models.Model): ot_losses = models.PositiveSmallIntegerField(default=0) ot_wins = models.PositiveSmallIntegerField(default=0) ties = models.PositiveSmallIntegerField(default=0) + points = models.PositiveIntegerField(default=0) def __str__(self): return self.team.name @@ -72,6 +76,11 @@ def __str__(self): return self.name +class LeagueFreeAgent(models.Model): + user = models.ForeignKey(UserProfile, related_name='fa_profile', on_delete=models.CASCADE) + description = models.TextField(default="Include information about Free Agent here") + + class League(models.Model): name = models.CharField(default="League Name", max_length=50) settings = models.ForeignKey(LeagueSettings, related_name="league_settings", on_delete=models.PROTECT) @@ -108,5 +117,6 @@ class League(models.Model): prize2 = models.CharField(default='no prize specified', max_length=50) prize3 = models.CharField(default='no prize specified', max_length=50) teams = models.ManyToManyField(LeagueTeam, blank=True) + fa = models.ManyToManyField(LeagueFreeAgent, related_name="league_fas", blank=True) From 2f0ef7185733561457bd24706f5adb59fa28f078 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 17:35:38 -0400 Subject: [PATCH 047/190] sort teams by points in division detail view --- leagues/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/leagues/views.py b/leagues/views.py index 2aa46b8da..3818b894b 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -42,7 +42,9 @@ def detail_league_division(request, pk, divid): league = get_object_or_404(League) division = LeagueDivision.objects.get(pk=divid) matches = division.matches.all() - return render(request, 'leagues/league_division.html', {'league': league, 'division': division, 'matches': matches}) + teams = division.teams.all().order_by('-points') + return render(request, 'leagues/league_division.html', {'league': league, 'division': division, 'matches': matches, + 'teams': teams}) def detail_league_rules(request, pk): From 3e2a4c99145c5595dc2528b93b432a96708e4308 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 17:36:04 -0400 Subject: [PATCH 048/190] Show division standings in division detail template --- .../leagues/league_division.html | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/project-templates/leagues/league_division.html b/project-templates/leagues/league_division.html index 668336744..a8638d894 100644 --- a/project-templates/leagues/league_division.html +++ b/project-templates/leagues/league_division.html @@ -8,6 +8,32 @@ {% block body %} + +

Division Standings

+ + + + + + + + + + {% for team in teams %} + + + + + + + + {% endfor %} + + +
TeamWins/LossPoints
{{ team.team.name }}{{ team.wins }}/{{ team.losses }}{{ team.points }}
+ + +

Division Matches

From e29db655fb515df3ec6d5f3f7d6c9c5c133071ba Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 17:36:27 -0400 Subject: [PATCH 049/190] fix typo in staff:league division detail template --- project-templates/staff/leagues/league_division_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project-templates/staff/leagues/league_division_detail.html b/project-templates/staff/leagues/league_division_detail.html index 17edd63e5..8bae21df0 100644 --- a/project-templates/staff/leagues/league_division_detail.html +++ b/project-templates/staff/leagues/league_division_detail.html @@ -8,7 +8,7 @@ {% block body %} -

Back to Tournament +

Back to League list

From ce1db6c214c24cb1b65ab60e32c06f7e79417d1a Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 18:01:13 -0400 Subject: [PATCH 050/190] add TODO for #107, add league matches list in staff view and template --- project-templates/staff/matches/matches.html | 54 ++++++++++++++++++++ staff/views/matches.py | 42 +++++++++------ 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/project-templates/staff/matches/matches.html b/project-templates/staff/matches/matches.html index d2e2403ac..d5d869bac 100644 --- a/project-templates/staff/matches/matches.html +++ b/project-templates/staff/matches/matches.html @@ -115,5 +115,59 @@

Wager Matches

{% endfor %}
+

League Matches

+ + + + + + + + + + + + + + {% for match in lmatches %} + + + + + + + + + + + + + {% if match.completed %} + + {% elif not match.completed %} + + {% endif %} + + + + {% if not match.bye_1 and not match.bye_2 %} + + {% else %} + + {% endif %} + + {% if not match.completed %} + + {% else %} + + {% endif %} + + + {% endfor %} +
IDPlatformGameAway TeamHome TeamCompletedSizeByes
#{{ match.id }}{{ match.platform.name }} + {{ match.game.name }}{{ match.awayteam.name }}{{ match.hometeam.name }}YesNoSomethingNoYesDeclare + WinnerDelete + Winner
+ {% endblock %} \ No newline at end of file diff --git a/staff/views/matches.py b/staff/views/matches.py index e1e63880c..117166bb6 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -21,7 +21,8 @@ def matches_index(request): # matches_list = Match.objects.all().order_by('-id') tmatches = Match.objects.filter(type__isnull=True) wmatches = Match.objects.filter(type='w') - return render(request, 'staff/matches/matches.html', {'tmatches': tmatches, 'wmatches': wmatches}) + lmatches = Match.objects.filter(type="leagues") + return render(request, 'staff/matches/matches.html', {'tmatches': tmatches, 'wmatches': wmatches, 'lmatches': lmatches}) def disputed_matches(request): @@ -93,6 +94,9 @@ def post(self, request, pk): return render(request, 'staff/permissiondenied.html') else: matchobj = Match.objects.get(pk=pk) + if matchobj.type == "league": + # TODO: 107 + pass if not matchobj.bye_2 and not matchobj.bye_1: form = DeclareMatchWinnerPost(request.POST, instance=matchobj) instance = form.instance @@ -139,23 +143,27 @@ def match_delete_winner(request, pk): return render(request, 'staff/permissiondenied.html') else: match = Match.objects.get(pk=pk) - if not match.bye_1 and not match.bye_2: - match.winner = None - match.completed = False - match.reported = False - match.team1reported = False - match.team2reported = False - match.team1reportedwinner = None - match.team2reportedwinner = None - match.disputed = False - match.save() - for i in MatchReport.objects.filter(match_id=pk): - i.delete() - messages.success(request, "Winner reset") - return redirect('staff:matches_index') + if match.type == "league": + # TODO: #107 + pass else: - messages.error(request, 'Bye match, cannot change winner') - return redirect('staff:matches_index') + if not match.bye_1 and not match.bye_2: + match.winner = None + match.completed = False + match.reported = False + match.team1reported = False + match.team2reported = False + match.team1reportedwinner = None + match.team2reportedwinner = None + match.disputed = False + match.save() + for i in MatchReport.objects.filter(match_id=pk): + i.delete() + messages.success(request, "Winner reset") + return redirect('staff:matches_index') + else: + messages.error(request, 'Bye match, cannot change winner') + return redirect('staff:matches_index') def dispute_detail(request, pk): From 5c83a5c9b3f60e8bf728564ce8ce5cb23283c2ad Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 18:01:23 -0400 Subject: [PATCH 051/190] add league settings detail template --- .../staff/leagues/league_settings_detail.html | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/project-templates/staff/leagues/league_settings_detail.html b/project-templates/staff/leagues/league_settings_detail.html index e69de29bb..302a2ba38 100644 --- a/project-templates/staff/leagues/league_settings_detail.html +++ b/project-templates/staff/leagues/league_settings_detail.html @@ -0,0 +1,102 @@ +{% extends 'staff/staffbase.html' %} + +{% load static %} + +{% block title %} + League Settings {{ settings.pk }} +{% endblock %} +{% block body %} + + + + + + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endif %} +

Return to league settings list

+
+ Create a new ruleset +
+ + + + {{ settings.name }} +
+ + + {{ settings.ot_losses }} +
+ + + {{ settings.pts_ot_loss }} +
+ + + {{ settings.ot_wins }} +
+ + + {{ settings.pts_ot_win }} +
+ + + {{ settings.pts_win }} +
+ + + {{ settings.pts_loss }} +
+ + + {{ settings.allow_tie }} +
+ + + {{ settings.num_games }} +
+ + + {{ settings.auto_schedule }} +
+ + + + {{ settings.auto_matchup }} +
+ + + + + {{ settings.num_divisions }} +
+ + + {{ settings.max_division_size }} +
+ + + EDIT + + + +{% endblock %} \ No newline at end of file From 296de66d4e03e480524128e021d67e036adf2262 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 3 Sep 2020 18:01:45 -0400 Subject: [PATCH 052/190] front end league teams template for standings --- project-templates/leagues/league_teams.html | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/project-templates/leagues/league_teams.html b/project-templates/leagues/league_teams.html index e69de29bb..fe4b25e4b 100644 --- a/project-templates/leagues/league_teams.html +++ b/project-templates/leagues/league_teams.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% load static %} + +{% block head %} + Division #{{ division.id }} Matches - {{ SITE_NAME }} +{% endblock %} + + +{% block body %} + + +

Division Standings

+ + + + + + + + + + {% for team in teams %} + + + + + + + + {% endfor %} + + +
TeamWins/LossPoints
{{ team.team.name }}{{ team.wins }}/{{ team.losses }}{{ team.points }}
+ +{% endblock %} \ No newline at end of file From 6204f0c0f527ebe55306e33870cf12068e192073 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:07:03 -0500 Subject: [PATCH 053/190] match checkin templates initial commit --- project-templates/matches/match_checkin.html | 0 project-templates/matches/team_checkin.html | 0 project-templates/staff/matches/checkins.html | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 project-templates/matches/match_checkin.html create mode 100644 project-templates/matches/team_checkin.html create mode 100644 project-templates/staff/matches/checkins.html diff --git a/project-templates/matches/match_checkin.html b/project-templates/matches/match_checkin.html new file mode 100644 index 000000000..e69de29bb diff --git a/project-templates/matches/team_checkin.html b/project-templates/matches/team_checkin.html new file mode 100644 index 000000000..e69de29bb diff --git a/project-templates/staff/matches/checkins.html b/project-templates/staff/matches/checkins.html new file mode 100644 index 000000000..e69de29bb From 9d3b4347441e4caf829578640dea513c53883dea Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:07:30 -0500 Subject: [PATCH 054/190] staff match checkin list template show all match checkins for a specific match. --- project-templates/staff/matches/checkins.html | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/project-templates/staff/matches/checkins.html b/project-templates/staff/matches/checkins.html index e69de29bb..46110323f 100644 --- a/project-templates/staff/matches/checkins.html +++ b/project-templates/staff/matches/checkins.html @@ -0,0 +1,33 @@ +{% extends 'staff/staffbase.html' %} +{% load static %} + + +{% block title %} + Matches #{{ mymatch.pk }} Checkins +{% endblock %} + +{% block body %} +
+

Match #{{ mymatch.pk }} Checkins

+ + + + + + + + {% for checkin in checkins %} + + + + + + + + {% endfor %} +
Checkin IDReporterTeamPlayers
{{ checkin.pk }} - DELETE CHECKIN{{ checkin.reporter }}{{ checkin.team.name }}{% for x in checkin.players %} + {{ x }}
+ {% endfor %} +
+
+{% endblock %} From 9ad8f81c1541f954c219a4e226db47c776540aad Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:08:23 -0500 Subject: [PATCH 055/190] match checkin form information - temp todo: fix queryset for the players field so it grabs all players from the specific team --- matches/forms.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matches/forms.py b/matches/forms.py index 43c44b3e5..90ac10829 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -1,6 +1,6 @@ from django import forms -from matches.models import MatchReport, MatchDispute, Match +from matches.models import MatchReport, MatchDispute, Match, MatchCheckIn from teams.models import Team @@ -37,3 +37,10 @@ class DisputeCreateForm(forms.ModelForm): class Meta: model = MatchDispute fields = ['teamproof_1', 'teamproof_2', 'teamproof_3'] + + +class TeamCheckInForm(forms.Form): + #TODO: define queryset for players team checkin form + def __init__(self, team): + mylist = team.players + team.founder + team.captain + players = forms.ModelMultipleChoiceField(queryset=None) \ No newline at end of file From 4cef36a0ed1e52be8375ed89c61c880a726b1b4c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:08:41 -0500 Subject: [PATCH 056/190] match checkin template for front end, redirect user to one of two pages --- project-templates/matches/match_checkin.html | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/project-templates/matches/match_checkin.html b/project-templates/matches/match_checkin.html index e69de29bb..db9c7d70a 100644 --- a/project-templates/matches/match_checkin.html +++ b/project-templates/matches/match_checkin.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% load static %} + +{% block head %} + Match Checkin #{{ match.pk }} - {{ SITE_NAME }} +{% endblock %} + +{% block body %} + Checkin Information for Match #{{ match.pk }} + + + + + + + + + + + + + + + + + + + + + +
Match Type{{ match.type }}
Platform - Game{{ match.platform.name }} - {{ match.game.name }}
Match Info{{ match.info }}
Home Team{{ match.hometeam.name }}
Away Team{{ match.awayteam.name }}
+

Please note only captains and team founder's can checkin for matches

+

To proceed with match checkin process, please click the corresponding button below.
+ You will be redirected to a new page where you will select the players that will be playing in the match

+ + +{% endblock %} \ No newline at end of file From 1994006a1699c9f412b92b7785b094a324576c25 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:09:04 -0500 Subject: [PATCH 057/190] team specific checkin template, output form from match.views --- project-templates/matches/team_checkin.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/project-templates/matches/team_checkin.html b/project-templates/matches/team_checkin.html index e69de29bb..210f31638 100644 --- a/project-templates/matches/team_checkin.html +++ b/project-templates/matches/team_checkin.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% load static %} + +{% block head %} + Team Checkin #{{ match.pk }} - {{ SITE_NAME }} +{% endblock %} + +{% block body %} +

Please select the players from your team below that are playing in the match

+ {{ form.as_table}} +{% endblock %} \ No newline at end of file From 8e066c6fc979e4617adcb84926030d6ef6cbc996 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:09:30 -0500 Subject: [PATCH 058/190] start work on some match stats, add matchchekin staff logic --- staff/views/matches.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index 9f306b7fb..b7cc28423 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -2,7 +2,7 @@ from django.shortcuts import render, redirect from django.views.generic import View -from matches.models import MatchReport, MatchDispute +from matches.models import * from staff.forms import * from wagers.models import * @@ -481,3 +481,38 @@ def create_map_pool_choice(request): else: form = GameChoiceForm(request.POST, request.FILES) return render(request, 'staff/matches/editmappool.html', {'form': form}) + + +def match_checkins(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + mymatch = Match.objects.get(pk=pk) + checkins = MatchCheckIn.objects.filter(match=mymatch) + return render(request, 'staff/matches/checkins.html', {'checkins': checkins, 'mymatch':mymatch}) + + +def delete_checkin(request, pk, checkinid): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + checkin = MatchCheckIn.objects.get(pk=pk) + checkin.delete() + checkin.save() + messages.success(request, "Checkin #"+checkin.pk+" has been deleted") + return redirect('staff:index') + +def match_stats_create(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + match = Match.objects.get(pk=pk) + stats = MatchStats() + team1 = match.awayteam + team2 = match.hometeam From ea28d7c62bf71e5ffe6c5f61fdbda1beb6918198 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:09:52 -0500 Subject: [PATCH 059/190] urls for staff match checkin views --- staff/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/staff/urls.py b/staff/urls.py index 384ab6ab6..3ec791750 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -82,6 +82,8 @@ path('round//edit', login_required(views.edit_round), name='edit_round'), path('round//', login_required(views.round_detail), name='round_detail'), path('dispute//', login_required(views.dispute_detail), name='dispute_detail'), + path('match//checkins/', login_required(views.match_checkins), name='match_checkins'), + path('match//checkins//delete/', login_required(views.delete_checkin), name='delete_match_checkin'), path('games/', login_required(views.gamelist), name='gamelist'), path('games//', login_required(views.game_detail), name='game_detail'), From 1d2959e01891ea2d272fa0f3ed393f91e68835b9 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 11:10:15 -0500 Subject: [PATCH 060/190] add matchcheckin models, and more match stats models --- .../0024_matchcheckin_teammatchstats.py | 37 +++++++++++++++++++ matches/models.py | 16 +++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 matches/migrations/0024_matchcheckin_teammatchstats.py diff --git a/matches/migrations/0024_matchcheckin_teammatchstats.py b/matches/migrations/0024_matchcheckin_teammatchstats.py new file mode 100644 index 000000000..24afbf70c --- /dev/null +++ b/matches/migrations/0024_matchcheckin_teammatchstats.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.15 on 2020-11-13 16:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0008_team_image'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('matches', '0023_matchstats_statsplayer'), + ] + + operations = [ + migrations.CreateModel( + name='TeamMatchStats', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rounds_won', models.PositiveSmallIntegerField(default=0)), + ('rounds_lost', models.PositiveSmallIntegerField(default=0)), + ('total_kills', models.PositiveSmallIntegerField(default=0)), + ('total_deaths', models.PositiveSmallIntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='MatchCheckIn', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('match', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_checkin', to='matches.Match')), + ('players', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkin_user', to=settings.AUTH_USER_MODEL)), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checking_in_team', to='teams.Team')), + ], + ), + ] diff --git a/matches/models.py b/matches/models.py index e2f513a3c..e8eb9cbbb 100644 --- a/matches/models.py +++ b/matches/models.py @@ -37,6 +37,13 @@ class StatsPlayer(models.Model): damage = models.IntegerField(default=0) +class TeamMatchStats(models.Model): + rounds_won = models.PositiveSmallIntegerField(default=0) + rounds_lost = models.PositiveSmallIntegerField(default=0) + total_kills = models.PositiveSmallIntegerField(default=0) + total_deaths = models.PositiveSmallIntegerField(default=0) + + class MatchStats(models.Model): matchid = models.PositiveIntegerField(default=0) map = models.CharField(default="unknown", max_length=255) @@ -44,7 +51,6 @@ class MatchStats(models.Model): team2 = models.CharField(default="unknown", max_length=255) - class SportChoice(models.Model): name = models.CharField(default='unknown sports', null=False, max_length=255) @@ -176,6 +182,14 @@ def get_min_team_size(self): return 6 +class MatchCheckIn(models.Model): + match = models.ForeignKey(Match, related_name='match_checkin', on_delete=models.SET_NULL, null=True) + reporter = models.ForeignKey(User, related_name='checkin_user', on_delete=models.SET_NULL, null=True) + team = models.ForeignKey(Team, related_name='checking_in_team', on_delete=models.SET_NULL, null=True) + # players should == match.get_min_team_size + players = models.ManyToManyField(User) + + class MatchReport(models.Model): created = models.DateTimeField(auto_now_add=True) # see the time the report was made for admins From 4dbc824867b8e4966994a16574dd2a3d94c2e169 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 15:42:09 -0500 Subject: [PATCH 061/190] fixed invalid field name on match.disable_userreport --- matches/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matches/views.py b/matches/views.py index afff4b6ab..27c23d424 100644 --- a/matches/views.py +++ b/matches/views.py @@ -63,7 +63,7 @@ class MatchReportCreateView(View): def get(self, request, pk): form = MatchReportCreateFormGet(request, pk) match = get_object_or_404(Match, pk) - if match.disable_userreports: + if match.disable_userreport: # user reports are disabled, return them to the match detail page with an error messages.error(request, "Match reports are disabled for this match") return redirect('matches:detail', pk=pk) From aa66edbef234745cc6479623a832a15ae19405e3 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 15:42:36 -0500 Subject: [PATCH 062/190] league detail show matches if only 1 division stub out join league view function --- leagues/views.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/leagues/views.py b/leagues/views.py index 2aa46b8da..7dafa08a1 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -10,16 +10,33 @@ def list_leagues(request): def detail_league(request, pk): - league = get_object_or_404(League) + league = get_object_or_404(League, pk=pk) teams = league.teams.all() + if len(league.divisions) == 1: + # there is only one division show the matches in the one page + division = league.divisions.first() + matches = division.matches + return render(request, 'leagues/league_division.html', + {'league': league, 'matches': matches, 'division': division}) return render(request, 'leagues/league_detail.html', {'league': league, 'teams': teams}) def join_league(request, pk): - pass + league = get_object_or_404(League, pk=pk) + # TODO - create join league form and send to template + if request.method == 'GET': + # send the form + pass + elif request.method == 'POST': + # try and get them to join the league + # make sure enough players exist on the team + pass + + return render(request, 'leagues/league_join.html', {'league': league}) def leave_league(request, pk): + # find out which team wants to leave pass @@ -28,7 +45,7 @@ def detail_league_teams(request, pk): def list_league_divisions(request, pk): - league = get_object_or_404(League) + league = get_object_or_404(League, pk=pk) if league.divisions.count() == 0: messages.warning(request, "There are no divisions for this league yet") return redirect('league:detail', pk) @@ -39,7 +56,7 @@ def list_league_divisions(request, pk): def detail_league_division(request, pk, divid): - league = get_object_or_404(League) + league = get_object_or_404(League, pk=pk) division = LeagueDivision.objects.get(pk=divid) matches = division.matches.all() return render(request, 'leagues/league_division.html', {'league': league, 'division': division, 'matches': matches}) From 9b39b289a7314c39c1f66bf8066da2996d3127e8 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 13 Nov 2020 15:43:01 -0500 Subject: [PATCH 063/190] stub out create match config function for get5. upload example get5 config soon --- staff/views/matches.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/staff/views/matches.py b/staff/views/matches.py index b7cc28423..2f65cb176 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -506,6 +506,7 @@ def delete_checkin(request, pk, checkinid): messages.success(request, "Checkin #"+checkin.pk+" has been deleted") return redirect('staff:index') + def match_stats_create(request, pk): user = UserProfile.objects.get(user__username=request.user.username) allowed = ['superadmin', 'admin'] @@ -516,3 +517,13 @@ def match_stats_create(request, pk): stats = MatchStats() team1 = match.awayteam team2 = match.hometeam + + +def create_match_config(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + # create the get5 config for the match + pass \ No newline at end of file From cdc2422952d526d4ec06e42d9dba1d14b1a888af Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 17 Nov 2020 11:45:20 -0500 Subject: [PATCH 064/190] reset all migrations --- leagues/migrations/0001_initial.py | 30 ++- leagues/migrations/0002_league_maps.py | 20 -- leagues/migrations/0003_auto_20200515_2139.py | 23 -- leagues/migrations/0004_auto_20200515_2205.py | 18 -- ...005_remove_leaguesettings_record_format.py | 17 -- .../0006_league_disable_userreport.py | 18 -- leagues/migrations/0007_auto_20200516_1809.py | 18 -- leagues/migrations/0008_auto_20200516_1814.py | 19 -- leagues/migrations/0009_auto_20200516_1815.py | 18 -- leagues/migrations/0010_auto_20200516_1817.py | 29 --- leagues/migrations/0011_auto_20200516_1817.py | 18 -- leagues/migrations/0012_auto_20200516_1818.py | 19 -- .../0013_leaguesettings_auto_matchup.py | 18 -- leagues/migrations/0014_auto_20200530_1901.py | 18 -- leagues/migrations/0015_auto_20200726_1705.py | 28 --- leagues/migrations/0016_league_teams.py | 18 -- matches/migrations/0001_initial.py | 222 +++++++++++++----- matches/migrations/0002_auto_20181012_0254.py | 22 -- matches/migrations/0003_match_info.py | 17 -- matches/migrations/0004_auto_20181018_2054.py | 19 -- matches/migrations/0005_auto_20181029_0402.py | 22 -- .../0006_gamechoice_platformchoice.py | 28 --- matches/migrations/0007_auto_20190112_1514.py | 25 -- matches/migrations/0008_auto_20190315_2152.py | 42 ---- matches/migrations/0008_match_type.py | 18 -- matches/migrations/0009_auto_20190328_1906.py | 18 -- matches/migrations/0010_auto_20190328_2021.py | 18 -- matches/migrations/0011_auto_20190328_2021.py | 18 -- matches/migrations/0012_auto_20190328_2021.py | 18 -- matches/migrations/0013_auto_20190328_2022.py | 18 -- .../migrations/0014_merge_20190605_1915.py | 14 -- matches/migrations/0015_auto_20190713_1335.py | 100 -------- matches/migrations/0016_auto_20200108_1654.py | 18 -- matches/migrations/0017_sportchoice.py | 20 -- .../0018_match_disable_userreports.py | 18 -- matches/migrations/0019_auto_20200115_1127.py | 29 --- matches/migrations/0020_auto_20200115_1132.py | 18 -- matches/migrations/0021_auto_20200115_1133.py | 18 -- matches/migrations/0022_auto_20200410_1506.py | 18 -- .../migrations/0023_matchstats_statsplayer.py | 58 ----- .../0024_matchcheckin_teammatchstats.py | 37 --- news/migrations/0001_initial.py | 40 ++-- news/migrations/0002_auto_20190713_1335.py | 20 -- news/migrations/0003_auto_20200507_1737.py | 19 -- news/migrations/0004_auto_20200507_1741.py | 19 -- pages/fixtures/pages.json | 32 --- pages/migrations/0001_initial.py | 53 ++++- pages/migrations/0002_auto_20180916_1705.py | 22 -- pages/migrations/0003_auto_20180920_2018.py | 41 ---- pages/migrations/0004_auto_20181008_1545.py | 87 ------- pages/migrations/0005_auto_20181012_1849.py | 27 --- pages/migrations/0006_auto_20181012_1953.py | 102 -------- pages/migrations/0007_auto_20190206_1617.py | 37 --- pages/migrations/0008_auto_20190206_1624.py | 37 --- pages/migrations/0009_auto_20190206_1628.py | 42 ---- pages/migrations/0010_auto_20190207_1944.py | 22 -- pages/migrations/0011_auto_20190217_1554.py | 48 ---- pages/migrations/0012_auto_20190312_1956.py | 63 ----- pages/migrations/0013_auto_20190313_1311.py | 25 -- ...4_remove_staticinfo_featured_touranment.py | 17 -- .../0015_staticinfo_featured_touranment.py | 20 -- pages/migrations/0016_auto_20190327_1727.py | 48 ---- pages/migrations/0017_auto_20190328_1523.py | 24 -- pages/migrations/0018_auto_20190329_1930.py | 19 -- pages/migrations/0018_auto_20190426_1727.py | 20 -- pages/migrations/0019_auto_20190523_2257.py | 49 ---- pages/migrations/0019_merge_20190605_1915.py | 14 -- pages/migrations/0020_auto_20190605_1916.py | 19 -- pages/migrations/0021_merge_20190607_1511.py | 14 -- pages/migrations/0022_auto_20190620_1748.py | 20 -- pages/migrations/0023_auto_20190620_1749.py | 20 -- pages/migrations/0024_auto_20200108_1654.py | 141 ----------- pages/migrations/0025_auto_20200410_1506.py | 88 ------- pages/migrations/0026_auto_20200410_1525.py | 46 ---- pages/migrations/0027_auto_20200410_1526.py | 53 ----- pages/migrations/0028_auto_20200410_1534.py | 28 --- pages/migrations/0029_auto_20200420_1350.py | 82 ------- profiles/migrations/0001_initial.py | 50 ++-- .../migrations/0002_auto_20180803_1110.py | 17 -- profiles/migrations/0003_userprofile_rank.py | 17 -- .../0004_userprofile_user_verified.py | 17 -- profiles/migrations/0005_userprofile_epic.py | 17 -- .../migrations/0006_auto_20190207_1944.py | 22 -- .../0007_userprofile_email_enabled.py | 17 -- .../migrations/0008_auto_20190602_1757.py | 18 -- .../migrations/0009_auto_20190620_1725.py | 22 -- profiles/migrations/0010_delete_usergear.py | 16 -- .../0011_userprofile_activisionid.py | 18 -- singletournaments/migrations/0001_initial.py | 81 +++---- .../0002_singleeliminationtournament_third.py | 20 -- .../migrations/0003_auto_20180914_1431.py | 19 -- .../0004_singleeliminationtournament_image.py | 17 -- ...004_singleeliminationtournament_xp_seed.py | 17 -- .../migrations/0005_auto_20181012_0254.py | 22 -- .../migrations/0006_auto_20181018_2054.py | 19 -- ...gleeliminationtournament_allow_register.py | 17 -- .../migrations/0008_merge_20181109_2110.py | 13 - ...0008_singleeliminationtournament_twitch.py | 17 -- .../0009_singletournamentround_info.py | 17 -- .../migrations/0010_merge_20181118_0553.py | 13 - .../migrations/0011_auto_20190112_1515.py | 22 -- .../migrations/0012_auto_20190112_1546.py | 23 -- .../migrations/0013_auto_20190313_1400.py | 18 -- .../migrations/0014_auto_20190329_1944.py | 19 -- ...14_singleeliminationtournament_map_pool.py | 20 -- .../migrations/0015_merge_20190602_1430.py | 14 -- .../migrations/0016_auto_20190713_1335.py | 55 ----- ...iminationtournament_disable_userreports.py | 18 -- .../migrations/0018_auto_20200115_1122.py | 30 --- .../migrations/0019_auto_20200115_1132.py | 18 -- .../migrations/0020_auto_20200115_1133.py | 18 -- .../migrations/0021_auto_20200420_0007.py | 29 --- store/migrations/0001_initial.py | 9 +- store/migrations/0002_remove_product_price.py | 17 -- support/migrations/0001_initial.py | 59 +++-- support/migrations/0002_auto_20190323_1953.py | 27 --- support/migrations/0003_auto_20190713_1335.py | 25 -- ...4_questionanswer_questionanswercategory.py | 35 --- teams/migrations/0001_initial.py | 35 +-- teams/migrations/0002_auto_20181007_0517.py | 22 -- teams/migrations/0002_team_totalxp.py | 17 -- teams/migrations/0003_auto_20181012_2134.py | 17 -- teams/migrations/0004_merge_20181109_2114.py | 13 - teams/migrations/0005_team_country.py | 19 -- teams/migrations/0006_auto_20190620_1746.py | 22 -- teams/migrations/0007_auto_20190713_1335.py | 20 -- teams/migrations/0008_team_image.py | 18 -- wagers/migrations/0001_initial.py | 19 +- .../migrations/0002_wagerrequest_expired.py | 18 -- wagers/migrations/0003_auto_20190426_1731.py | 20 -- .../0004_wagerchallenge_confirmed.py | 17 -- .../migrations/0005_wagerrequest_challenge.py | 19 -- wagers/migrations/0006_auto_20190514_1450.py | 18 -- wagers/migrations/0007_auto_20190523_1839.py | 18 -- wagers/migrations/0008_wagermatch_credits.py | 18 -- wagers/migrations/0009_wagerrequest_wmatch.py | 19 -- wagers/migrations/0010_auto_20190713_1335.py | 24 -- 137 files changed, 378 insertions(+), 3594 deletions(-) delete mode 100644 leagues/migrations/0002_league_maps.py delete mode 100644 leagues/migrations/0003_auto_20200515_2139.py delete mode 100644 leagues/migrations/0004_auto_20200515_2205.py delete mode 100644 leagues/migrations/0005_remove_leaguesettings_record_format.py delete mode 100644 leagues/migrations/0006_league_disable_userreport.py delete mode 100644 leagues/migrations/0007_auto_20200516_1809.py delete mode 100644 leagues/migrations/0008_auto_20200516_1814.py delete mode 100644 leagues/migrations/0009_auto_20200516_1815.py delete mode 100644 leagues/migrations/0010_auto_20200516_1817.py delete mode 100644 leagues/migrations/0011_auto_20200516_1817.py delete mode 100644 leagues/migrations/0012_auto_20200516_1818.py delete mode 100644 leagues/migrations/0013_leaguesettings_auto_matchup.py delete mode 100644 leagues/migrations/0014_auto_20200530_1901.py delete mode 100644 leagues/migrations/0015_auto_20200726_1705.py delete mode 100644 leagues/migrations/0016_league_teams.py delete mode 100644 matches/migrations/0002_auto_20181012_0254.py delete mode 100644 matches/migrations/0003_match_info.py delete mode 100644 matches/migrations/0004_auto_20181018_2054.py delete mode 100644 matches/migrations/0005_auto_20181029_0402.py delete mode 100644 matches/migrations/0006_gamechoice_platformchoice.py delete mode 100644 matches/migrations/0007_auto_20190112_1514.py delete mode 100644 matches/migrations/0008_auto_20190315_2152.py delete mode 100644 matches/migrations/0008_match_type.py delete mode 100644 matches/migrations/0009_auto_20190328_1906.py delete mode 100644 matches/migrations/0010_auto_20190328_2021.py delete mode 100644 matches/migrations/0011_auto_20190328_2021.py delete mode 100644 matches/migrations/0012_auto_20190328_2021.py delete mode 100644 matches/migrations/0013_auto_20190328_2022.py delete mode 100644 matches/migrations/0014_merge_20190605_1915.py delete mode 100644 matches/migrations/0015_auto_20190713_1335.py delete mode 100644 matches/migrations/0016_auto_20200108_1654.py delete mode 100644 matches/migrations/0017_sportchoice.py delete mode 100644 matches/migrations/0018_match_disable_userreports.py delete mode 100644 matches/migrations/0019_auto_20200115_1127.py delete mode 100644 matches/migrations/0020_auto_20200115_1132.py delete mode 100644 matches/migrations/0021_auto_20200115_1133.py delete mode 100644 matches/migrations/0022_auto_20200410_1506.py delete mode 100644 matches/migrations/0023_matchstats_statsplayer.py delete mode 100644 matches/migrations/0024_matchcheckin_teammatchstats.py delete mode 100644 news/migrations/0002_auto_20190713_1335.py delete mode 100644 news/migrations/0003_auto_20200507_1737.py delete mode 100644 news/migrations/0004_auto_20200507_1741.py delete mode 100644 pages/fixtures/pages.json delete mode 100644 pages/migrations/0002_auto_20180916_1705.py delete mode 100644 pages/migrations/0003_auto_20180920_2018.py delete mode 100644 pages/migrations/0004_auto_20181008_1545.py delete mode 100644 pages/migrations/0005_auto_20181012_1849.py delete mode 100644 pages/migrations/0006_auto_20181012_1953.py delete mode 100644 pages/migrations/0007_auto_20190206_1617.py delete mode 100644 pages/migrations/0008_auto_20190206_1624.py delete mode 100644 pages/migrations/0009_auto_20190206_1628.py delete mode 100644 pages/migrations/0010_auto_20190207_1944.py delete mode 100644 pages/migrations/0011_auto_20190217_1554.py delete mode 100644 pages/migrations/0012_auto_20190312_1956.py delete mode 100644 pages/migrations/0013_auto_20190313_1311.py delete mode 100644 pages/migrations/0014_remove_staticinfo_featured_touranment.py delete mode 100644 pages/migrations/0015_staticinfo_featured_touranment.py delete mode 100644 pages/migrations/0016_auto_20190327_1727.py delete mode 100644 pages/migrations/0017_auto_20190328_1523.py delete mode 100644 pages/migrations/0018_auto_20190329_1930.py delete mode 100644 pages/migrations/0018_auto_20190426_1727.py delete mode 100644 pages/migrations/0019_auto_20190523_2257.py delete mode 100644 pages/migrations/0019_merge_20190605_1915.py delete mode 100644 pages/migrations/0020_auto_20190605_1916.py delete mode 100644 pages/migrations/0021_merge_20190607_1511.py delete mode 100644 pages/migrations/0022_auto_20190620_1748.py delete mode 100644 pages/migrations/0023_auto_20190620_1749.py delete mode 100644 pages/migrations/0024_auto_20200108_1654.py delete mode 100644 pages/migrations/0025_auto_20200410_1506.py delete mode 100644 pages/migrations/0026_auto_20200410_1525.py delete mode 100644 pages/migrations/0027_auto_20200410_1526.py delete mode 100644 pages/migrations/0028_auto_20200410_1534.py delete mode 100644 pages/migrations/0029_auto_20200420_1350.py delete mode 100644 profiles/migrations/0002_auto_20180803_1110.py delete mode 100644 profiles/migrations/0003_userprofile_rank.py delete mode 100644 profiles/migrations/0004_userprofile_user_verified.py delete mode 100644 profiles/migrations/0005_userprofile_epic.py delete mode 100644 profiles/migrations/0006_auto_20190207_1944.py delete mode 100644 profiles/migrations/0007_userprofile_email_enabled.py delete mode 100644 profiles/migrations/0008_auto_20190602_1757.py delete mode 100644 profiles/migrations/0009_auto_20190620_1725.py delete mode 100644 profiles/migrations/0010_delete_usergear.py delete mode 100644 profiles/migrations/0011_userprofile_activisionid.py delete mode 100644 singletournaments/migrations/0002_singleeliminationtournament_third.py delete mode 100644 singletournaments/migrations/0003_auto_20180914_1431.py delete mode 100644 singletournaments/migrations/0004_singleeliminationtournament_image.py delete mode 100644 singletournaments/migrations/0004_singleeliminationtournament_xp_seed.py delete mode 100644 singletournaments/migrations/0005_auto_20181012_0254.py delete mode 100644 singletournaments/migrations/0006_auto_20181018_2054.py delete mode 100644 singletournaments/migrations/0007_singleeliminationtournament_allow_register.py delete mode 100644 singletournaments/migrations/0008_merge_20181109_2110.py delete mode 100644 singletournaments/migrations/0008_singleeliminationtournament_twitch.py delete mode 100644 singletournaments/migrations/0009_singletournamentround_info.py delete mode 100644 singletournaments/migrations/0010_merge_20181118_0553.py delete mode 100644 singletournaments/migrations/0011_auto_20190112_1515.py delete mode 100644 singletournaments/migrations/0012_auto_20190112_1546.py delete mode 100644 singletournaments/migrations/0013_auto_20190313_1400.py delete mode 100644 singletournaments/migrations/0014_auto_20190329_1944.py delete mode 100644 singletournaments/migrations/0014_singleeliminationtournament_map_pool.py delete mode 100644 singletournaments/migrations/0015_merge_20190602_1430.py delete mode 100644 singletournaments/migrations/0016_auto_20190713_1335.py delete mode 100644 singletournaments/migrations/0017_singleeliminationtournament_disable_userreports.py delete mode 100644 singletournaments/migrations/0018_auto_20200115_1122.py delete mode 100644 singletournaments/migrations/0019_auto_20200115_1132.py delete mode 100644 singletournaments/migrations/0020_auto_20200115_1133.py delete mode 100644 singletournaments/migrations/0021_auto_20200420_0007.py delete mode 100644 store/migrations/0002_remove_product_price.py delete mode 100644 support/migrations/0002_auto_20190323_1953.py delete mode 100644 support/migrations/0003_auto_20190713_1335.py delete mode 100644 support/migrations/0004_questionanswer_questionanswercategory.py delete mode 100644 teams/migrations/0002_auto_20181007_0517.py delete mode 100644 teams/migrations/0002_team_totalxp.py delete mode 100644 teams/migrations/0003_auto_20181012_2134.py delete mode 100644 teams/migrations/0004_merge_20181109_2114.py delete mode 100644 teams/migrations/0005_team_country.py delete mode 100644 teams/migrations/0006_auto_20190620_1746.py delete mode 100644 teams/migrations/0007_auto_20190713_1335.py delete mode 100644 teams/migrations/0008_team_image.py delete mode 100644 wagers/migrations/0002_wagerrequest_expired.py delete mode 100644 wagers/migrations/0003_auto_20190426_1731.py delete mode 100644 wagers/migrations/0004_wagerchallenge_confirmed.py delete mode 100644 wagers/migrations/0005_wagerrequest_challenge.py delete mode 100644 wagers/migrations/0006_auto_20190514_1450.py delete mode 100644 wagers/migrations/0007_auto_20190523_1839.py delete mode 100644 wagers/migrations/0008_wagermatch_credits.py delete mode 100644 wagers/migrations/0009_wagerrequest_wmatch.py delete mode 100644 wagers/migrations/0010_auto_20190713_1335.py diff --git a/leagues/migrations/0001_initial.py b/leagues/migrations/0001_initial.py index 3fe93b481..16925a183 100644 --- a/leagues/migrations/0001_initial.py +++ b/leagues/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.12 on 2020-05-16 00:48 +# Generated by Django 2.2.15 on 2020-11-17 00:03 from django.db import migrations, models import django.db.models.deletion @@ -9,9 +9,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('matches', '0022_auto_20200410_1506'), - ('teams', '0008_team_image'), - ('singletournaments', '0021_auto_20200420_0007'), + ('singletournaments', '__first__'), + ('matches', '0001_initial'), + ('teams', '0001_initial'), ] operations = [ @@ -29,8 +29,8 @@ class Migration(migrations.Migration): ('allow_tie', models.BooleanField(default=False)), ('num_games', models.PositiveIntegerField(default=10)), ('auto_schedule', models.BooleanField(default=False)), - ('record_format', models.CharField(choices=[(1, 'W-L-OTL'), (2, 'W-L-T'), (3, 'W-L-OTW-OTL'), (4, 'W-L-OTW-OTL-OTT'), (5, 'W-L')], default='W-L-OTL', max_length=20)), - ('num_divisons', models.PositiveSmallIntegerField(default=2)), + ('auto_matchup', models.BooleanField(default=False)), + ('num_divisions', models.PositiveSmallIntegerField(default=2)), ('max_division_size', models.PositiveSmallIntegerField(default=5)), ], ), @@ -51,7 +51,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=50, null=True)), - ('games', models.ManyToManyField(blank=True, to='matches.Match')), + ('matches', models.ManyToManyField(blank=True, to='matches.Match')), ('teams', models.ManyToManyField(blank=True, to='leagues.LeagueTeam')), ], ), @@ -64,19 +64,27 @@ class Migration(migrations.Migration): ('info', models.TextField(default='No information provided')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('image', models.ImageField(blank=True, upload_to='league_images')), + ('image', models.ImageField(blank=True, null=True, upload_to='league_images')), ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), ('allow_register', models.BooleanField(default=False)), ('open_register', models.DateTimeField()), ('close_register', models.DateTimeField()), ('start', models.DateTimeField()), + ('req_credits', models.PositiveSmallIntegerField(default=0)), + ('size', models.PositiveSmallIntegerField(default=8)), + ('disable_userreport', models.BooleanField(default=False)), + ('prize1', models.CharField(default='no prize specified', max_length=50)), + ('prize2', models.CharField(default='no prize specified', max_length=50)), + ('prize3', models.CharField(default='no prize specified', max_length=50)), ('divisions', models.ManyToManyField(blank=True, to='leagues.LeagueDivision')), - ('game', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_game', to='matches.GameChoice')), - ('platform', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_platform', to='matches.PlatformChoice')), + ('game', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_game', to='matches.GameChoice')), + ('maps', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_maps', to='matches.MapPoolChoice')), + ('platform', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_platform', to='matches.PlatformChoice')), ('ruleset', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_ruleset', to='singletournaments.SingleTournamentRuleset')), ('settings', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_settings', to='leagues.LeagueSettings')), - ('sport', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_sport', to='matches.SportChoice')), + ('sport', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_sport', to='matches.SportChoice')), + ('teams', models.ManyToManyField(blank=True, to='leagues.LeagueTeam')), ], ), ] diff --git a/leagues/migrations/0002_league_maps.py b/leagues/migrations/0002_league_maps.py deleted file mode 100644 index 5d0a053f5..000000000 --- a/leagues/migrations/0002_league_maps.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 00:55 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0022_auto_20200410_1506'), - ('leagues', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='league', - name='maps', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_maps', to='matches.MapPoolChoice'), - ), - ] diff --git a/leagues/migrations/0003_auto_20200515_2139.py b/leagues/migrations/0003_auto_20200515_2139.py deleted file mode 100644 index 84e7b6b03..000000000 --- a/leagues/migrations/0003_auto_20200515_2139.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 01:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0002_league_maps'), - ] - - operations = [ - migrations.AddField( - model_name='league', - name='credits', - field=models.PositiveSmallIntegerField(default=0), - ), - migrations.AddField( - model_name='league', - name='size', - field=models.PositiveSmallIntegerField(default=8), - ), - ] diff --git a/leagues/migrations/0004_auto_20200515_2205.py b/leagues/migrations/0004_auto_20200515_2205.py deleted file mode 100644 index a5e3b1e05..000000000 --- a/leagues/migrations/0004_auto_20200515_2205.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 02:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0003_auto_20200515_2139'), - ] - - operations = [ - migrations.RenameField( - model_name='leaguesettings', - old_name='num_divisons', - new_name='num_divisions', - ), - ] diff --git a/leagues/migrations/0005_remove_leaguesettings_record_format.py b/leagues/migrations/0005_remove_leaguesettings_record_format.py deleted file mode 100644 index a5596d88f..000000000 --- a/leagues/migrations/0005_remove_leaguesettings_record_format.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 02:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0004_auto_20200515_2205'), - ] - - operations = [ - migrations.RemoveField( - model_name='leaguesettings', - name='record_format', - ), - ] diff --git a/leagues/migrations/0006_league_disable_userreport.py b/leagues/migrations/0006_league_disable_userreport.py deleted file mode 100644 index a12fd4a35..000000000 --- a/leagues/migrations/0006_league_disable_userreport.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0005_remove_leaguesettings_record_format'), - ] - - operations = [ - migrations.AddField( - model_name='league', - name='disable_userreport', - field=models.BooleanField(default=False), - ), - ] diff --git a/leagues/migrations/0007_auto_20200516_1809.py b/leagues/migrations/0007_auto_20200516_1809.py deleted file mode 100644 index 9d9b10c8b..000000000 --- a/leagues/migrations/0007_auto_20200516_1809.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0006_league_disable_userreport'), - ] - - operations = [ - migrations.RenameField( - model_name='league', - old_name='credits', - new_name='req_credits', - ), - ] diff --git a/leagues/migrations/0008_auto_20200516_1814.py b/leagues/migrations/0008_auto_20200516_1814.py deleted file mode 100644 index 64b19fe05..000000000 --- a/leagues/migrations/0008_auto_20200516_1814.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:14 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0007_auto_20200516_1809'), - ] - - operations = [ - migrations.AlterField( - model_name='league', - name='sport', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_sport', to='matches.SportChoice'), - ), - ] diff --git a/leagues/migrations/0009_auto_20200516_1815.py b/leagues/migrations/0009_auto_20200516_1815.py deleted file mode 100644 index 707d93cbb..000000000 --- a/leagues/migrations/0009_auto_20200516_1815.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0008_auto_20200516_1814'), - ] - - operations = [ - migrations.AlterField( - model_name='league', - name='divisions', - field=models.ManyToManyField(blank=True, null=True, to='leagues.LeagueDivision'), - ), - ] diff --git a/leagues/migrations/0010_auto_20200516_1817.py b/leagues/migrations/0010_auto_20200516_1817.py deleted file mode 100644 index f345b55ba..000000000 --- a/leagues/migrations/0010_auto_20200516_1817.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:17 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0009_auto_20200516_1815'), - ] - - operations = [ - migrations.AlterField( - model_name='league', - name='divisions', - field=models.ManyToManyField(blank=True, to='leagues.LeagueDivision'), - ), - migrations.AlterField( - model_name='league', - name='game', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_game', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='league', - name='platform', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_platform', to='matches.PlatformChoice'), - ), - ] diff --git a/leagues/migrations/0011_auto_20200516_1817.py b/leagues/migrations/0011_auto_20200516_1817.py deleted file mode 100644 index fab41006a..000000000 --- a/leagues/migrations/0011_auto_20200516_1817.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0010_auto_20200516_1817'), - ] - - operations = [ - migrations.AlterField( - model_name='league', - name='image', - field=models.ImageField(blank=True, null=True, upload_to='league_images'), - ), - ] diff --git a/leagues/migrations/0012_auto_20200516_1818.py b/leagues/migrations/0012_auto_20200516_1818.py deleted file mode 100644 index 3c998c9ba..000000000 --- a/leagues/migrations/0012_auto_20200516_1818.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-16 22:18 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0011_auto_20200516_1817'), - ] - - operations = [ - migrations.AlterField( - model_name='league', - name='maps', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_maps', to='matches.MapPoolChoice'), - ), - ] diff --git a/leagues/migrations/0013_leaguesettings_auto_matchup.py b/leagues/migrations/0013_leaguesettings_auto_matchup.py deleted file mode 100644 index f645200f2..000000000 --- a/leagues/migrations/0013_leaguesettings_auto_matchup.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-26 17:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0012_auto_20200516_1818'), - ] - - operations = [ - migrations.AddField( - model_name='leaguesettings', - name='auto_matchup', - field=models.BooleanField(default=False), - ), - ] diff --git a/leagues/migrations/0014_auto_20200530_1901.py b/leagues/migrations/0014_auto_20200530_1901.py deleted file mode 100644 index f189390d4..000000000 --- a/leagues/migrations/0014_auto_20200530_1901.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-30 23:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0013_leaguesettings_auto_matchup'), - ] - - operations = [ - migrations.RenameField( - model_name='leaguedivision', - old_name='games', - new_name='matches', - ), - ] diff --git a/leagues/migrations/0015_auto_20200726_1705.py b/leagues/migrations/0015_auto_20200726_1705.py deleted file mode 100644 index ed99033cb..000000000 --- a/leagues/migrations/0015_auto_20200726_1705.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.14 on 2020-07-26 21:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0014_auto_20200530_1901'), - ] - - operations = [ - migrations.AddField( - model_name='league', - name='prize1', - field=models.CharField(default='no prize specified', max_length=50), - ), - migrations.AddField( - model_name='league', - name='prize2', - field=models.CharField(default='no prize specified', max_length=50), - ), - migrations.AddField( - model_name='league', - name='prize3', - field=models.CharField(default='no prize specified', max_length=50), - ), - ] diff --git a/leagues/migrations/0016_league_teams.py b/leagues/migrations/0016_league_teams.py deleted file mode 100644 index 78948d236..000000000 --- a/leagues/migrations/0016_league_teams.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.14 on 2020-07-26 21:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0015_auto_20200726_1705'), - ] - - operations = [ - migrations.AddField( - model_name='league', - name='teams', - field=models.ManyToManyField(blank=True, to='leagues.LeagueTeam'), - ), - ] diff --git a/matches/migrations/0001_initial.py b/matches/migrations/0001_initial.py index 652ace4ee..f4845a474 100644 --- a/matches/migrations/0001_initial.py +++ b/matches/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:45 +# Generated by Django 2.2.15 on 2020-11-17 00:02 from django.conf import settings from django.db import migrations, models @@ -6,6 +6,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [ @@ -14,53 +15,134 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='GameChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='unknown', max_length=255)), + ('image', models.ImageField(blank=True, upload_to='game_images')), + ], + ), + migrations.CreateModel( + name='MapChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='default_map', max_length=255)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('map_num', models.IntegerField(blank=True, default=0, null=True)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_for', to='matches.GameChoice')), + ], + ), migrations.CreateModel( name='Match', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(blank=True, max_length=20, null=True)), ('matchnum', models.SmallIntegerField(default=0)), - ('game', models.SmallIntegerField( - choices=[(0, 'No Game Set'), (1, 'Call of Duty Black Ops 3'), (2, 'Call of Duty WWII'), - (3, 'Fortnite'), (4, 'Destiny 2'), (5, 'Counter-Strike: Global Offensive'), - (6, 'Player Unknowns Battlegrounds'), (7, 'Rainbow Six Siege'), (8, 'Overwatch'), - (9, 'League of Legends'), (10, 'Hearthstone'), (11, 'World of Warcraft'), (12, 'Smite'), - (13, 'Rocket League'), (14, 'Battlefield 1')], default=0)), - ('platform', models.SmallIntegerField( - choices=[(0, 'Playstation 4'), (1, 'Xbox One'), (2, 'PC'), (3, 'Mobile'), (4, 'Nintendo Switch'), - (5, 'Playstation 3'), (6, 'Xbox 360')], default=0)), ('reported', models.BooleanField(default=False)), ('completed', models.BooleanField(default=False)), - ('bestof', models.SmallIntegerField( - choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], - default=0)), - ('teamformat', models.SmallIntegerField( - choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), + ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), + ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), ('team1reported', models.BooleanField(default=False)), ('team2reported', models.BooleanField(default=False)), + ('info', models.TextField(default='Match Info: ')), ('disputed', models.BooleanField(default=False)), - ('awayteam', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='awayteam', - to='teams.Team')), - ('hometeam', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hometeam', - to='teams.Team')), - ('loser', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='loser', - to='teams.Team')), - ('team1reportedwinner', - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='team1reportedwinner', to='teams.Team')), - ('team2reportedwinner', - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='team2reportedwinner', to='teams.Team')), - ('winner', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='champions', - to='teams.Team')), + ('bye_1', models.BooleanField(default=False)), + ('bye_2', models.BooleanField(default=False)), + ('disable_userreport', models.BooleanField(default=True)), + ('awayteam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='awayteam', to='teams.Team')), + ('game', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='GameChoice', to='matches.GameChoice')), + ('hometeam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hometeam', to='teams.Team')), + ('loser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loser', to='teams.Team')), + ('map', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_map', to='matches.MapChoice')), ], options={ 'verbose_name_plural': 'matches', }, ), + migrations.CreateModel( + name='MatchStats', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('matchid', models.PositiveIntegerField(default=0)), + ('map', models.CharField(default='unknown', max_length=255)), + ('team1', models.CharField(default='unknown', max_length=255)), + ('team2', models.CharField(default='unknown', max_length=255)), + ], + ), + migrations.CreateModel( + name='PlatformChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='unknown', max_length=255)), + ('image', models.ImageField(blank=True, null=True, upload_to='platform_images')), + ], + ), + migrations.CreateModel( + name='SportChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='unknown sports', max_length=255)), + ], + ), + migrations.CreateModel( + name='StatsPlayer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.DecimalField(decimal_places=3, max_digits=6)), + ('kills', models.IntegerField(default=0)), + ('assists', models.IntegerField(default=0)), + ('deaths', models.IntegerField(default=0)), + ('killround', models.DecimalField(decimal_places=3, max_digits=6)), + ('adr', models.IntegerField(default=0)), + ('ud', models.IntegerField(default=0)), + ('ef', models.IntegerField(default=0)), + ('f_assists', models.IntegerField(default=0)), + ('hs', models.IntegerField(default=0)), + ('kast', models.IntegerField(default=0)), + ('awp_k', models.IntegerField(default=0)), + ('twok', models.IntegerField(default=0)), + ('threek', models.IntegerField(default=0)), + ('fourk', models.IntegerField(default=0)), + ('fivek', models.IntegerField(default=0)), + ('one_v_one', models.IntegerField(default=0)), + ('one_v_two', models.IntegerField(default=0)), + ('one_v_three', models.IntegerField(default=0)), + ('one_v_four', models.IntegerField(default=0)), + ('one_v_five', models.IntegerField(default=0)), + ('f_kills', models.IntegerField(default=0)), + ('f_deaths', models.IntegerField(default=0)), + ('entries', models.IntegerField(default=0)), + ('trades', models.IntegerField(default=0)), + ('rounds', models.IntegerField(default=0)), + ('rf', models.IntegerField(default=0)), + ('ra', models.IntegerField(default=0)), + ('damage', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='TeamMatchStats', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rounds_won', models.PositiveSmallIntegerField(default=0)), + ('rounds_lost', models.PositiveSmallIntegerField(default=0)), + ('total_kills', models.PositiveSmallIntegerField(default=0)), + ('total_deaths', models.PositiveSmallIntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='MatchReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('proof', models.CharField(default='no text inserted', max_length=300)), + ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matchreporting', to='matches.Match')), + ('reported_winner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winnerreporting', to='teams.Team')), + ('reporting_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teamreporting', to='teams.Team')), + ('reporting_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='userreporting', to=settings.AUTH_USER_MODEL)), + ], + ), migrations.CreateModel( name='MatchDispute', fields=[ @@ -70,39 +152,59 @@ class Migration(migrations.Migration): ('teamproof_1', models.URLField()), ('teamproof_2', models.URLField(blank=True)), ('teamproof_3', models.URLField(blank=True)), - ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='disputedMatch', - to='matches.Match')), - ('team1', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team1', to='teams.Team')), - ('team1origreporter', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team1OriginalReporter', - to=settings.AUTH_USER_MODEL)), - ('team2', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team2', to='teams.Team')), - ('team2origreporter', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team2OriginalReporter', - to=settings.AUTH_USER_MODEL)), - ('teamreporter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='team1Disputer', to=settings.AUTH_USER_MODEL)), + ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='disputedMatch', to='matches.Match')), + ('team1', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1', to='teams.Team')), + ('team1origreporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1OriginalReporter', to=settings.AUTH_USER_MODEL)), + ('team2', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2', to='teams.Team')), + ('team2origreporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2OriginalReporter', to=settings.AUTH_USER_MODEL)), + ('teamreporter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1Disputer', to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( - name='MatchReport', + name='MatchCheckIn', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('match', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_checkin', to='matches.Match')), + ('players', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkin_user', to=settings.AUTH_USER_MODEL)), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checking_in_team', to='teams.Team')), + ], + ), + migrations.AddField( + model_name='match', + name='platform', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='PlatformChoice', to='matches.PlatformChoice'), + ), + migrations.AddField( + model_name='match', + name='sport', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='SportChoice', to='matches.SportChoice'), + ), + migrations.AddField( + model_name='match', + name='team1reportedwinner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1reportedwinner', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='team2reportedwinner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2reportedwinner', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='winner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='champions', to='teams.Team'), + ), + migrations.CreateModel( + name='MapPoolChoice', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='default map pool', max_length=255)), + ('description', models.CharField(default='No map pool description', max_length=255)), ('created', models.DateTimeField(auto_now_add=True)), - ('proof', models.CharField(default='no text inserted', max_length=300)), - ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matchreporting', - to='matches.Match')), - ('reported_winner', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='winnerreporting', - to='teams.Team')), - ('reporting_team', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teamreporting', - to='teams.Team')), - ('reporting_user', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userreporting', - to=settings.AUTH_USER_MODEL)), + ('updated', models.DateTimeField(auto_now=True)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_pool_for', to='matches.GameChoice')), + ('maps', models.ManyToManyField(blank=True, to='matches.MapChoice')), ], ), ] diff --git a/matches/migrations/0002_auto_20181012_0254.py b/matches/migrations/0002_auto_20181012_0254.py deleted file mode 100644 index 08af2873f..000000000 --- a/matches/migrations/0002_auto_20181012_0254.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-12 02:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='game', - field=models.SmallIntegerField( - choices=[(0, 'No Game Set'), (1, 'Call of Duty Black Ops 3'), (2, 'Call of Duty WWII'), (3, 'Fortnite'), - (4, 'Destiny 2'), (5, 'Counter-Strike: Global Offensive'), - (6, 'Player Unknowns Battlegrounds'), (7, 'Rainbow Six Siege'), (8, 'Overwatch'), - (9, 'League of Legends'), (10, 'Hearthstone'), (11, 'World of Warcraft'), (12, 'Smite'), - (13, 'Rocket League'), (14, 'Battlefield 1'), (15, 'Black Ops 4')], default=0), - ), - ] diff --git a/matches/migrations/0003_match_info.py b/matches/migrations/0003_match_info.py deleted file mode 100644 index e4884e5aa..000000000 --- a/matches/migrations/0003_match_info.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.5 on 2018-10-13 20:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0002_auto_20181012_0254'), - ] - - operations = [ - migrations.AddField( - model_name='match', - name='info', - field=models.TextField(default='Match Info: '), - ), - ] diff --git a/matches/migrations/0004_auto_20181018_2054.py b/matches/migrations/0004_auto_20181018_2054.py deleted file mode 100644 index 137e1108a..000000000 --- a/matches/migrations/0004_auto_20181018_2054.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.4 on 2018-10-19 00:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0003_match_info'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='platform', - field=models.SmallIntegerField( - choices=[(0, 'Playstation 4'), (1, 'Xbox One'), (2, 'PC'), (3, 'Mobile'), (4, 'Nintendo Switch'), - (5, 'Playstation 3'), (6, 'Xbox 360'), (7, 'All Consoles'), (8, 'All Platforms')], default=0), - ), - ] diff --git a/matches/migrations/0005_auto_20181029_0402.py b/matches/migrations/0005_auto_20181029_0402.py deleted file mode 100644 index 9999ba1b3..000000000 --- a/matches/migrations/0005_auto_20181029_0402.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-29 04:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0004_auto_20181018_2054'), - ] - - operations = [ - migrations.AddField( - model_name='match', - name='bye_1', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='match', - name='bye_2', - field=models.BooleanField(default=False), - ), - ] diff --git a/matches/migrations/0006_gamechoice_platformchoice.py b/matches/migrations/0006_gamechoice_platformchoice.py deleted file mode 100644 index 3061950b2..000000000 --- a/matches/migrations/0006_gamechoice_platformchoice.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.0.7 on 2019-01-12 02:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0005_auto_20181029_0402'), - ] - - operations = [ - migrations.CreateModel( - name='GameChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='unknown', max_length=255)), - ('image', models.ImageField(blank=True, upload_to='game_images')), - ], - ), - migrations.CreateModel( - name='PlatformChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='unknown', max_length=255)), - ('image', models.ImageField(blank=True, null=True, upload_to='platform_images')), - ], - ), - ] diff --git a/matches/migrations/0007_auto_20190112_1514.py b/matches/migrations/0007_auto_20190112_1514.py deleted file mode 100644 index 6d033335d..000000000 --- a/matches/migrations/0007_auto_20190112_1514.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.0.4 on 2019-01-12 20:14 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('matches', '0006_gamechoice_platformchoice'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='GameChoice', - to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='match', - name='platform', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='PlatformChoice', - to='matches.PlatformChoice'), - ), - ] diff --git a/matches/migrations/0008_auto_20190315_2152.py b/matches/migrations/0008_auto_20190315_2152.py deleted file mode 100644 index e6c49bee1..000000000 --- a/matches/migrations/0008_auto_20190315_2152.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-16 01:52 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0007_auto_20190112_1514'), - ] - - operations = [ - migrations.CreateModel( - name='MapChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='default_map', max_length=255)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('map_num', models.IntegerField(default=0)), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_for', to='matches.GameChoice')), - ], - ), - migrations.CreateModel( - name='MapPoolChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='default map pool', max_length=255)), - ('description', models.CharField(default='No map pool description', max_length=255)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_pool_for', to='matches.GameChoice')), - ('maps', models.ManyToManyField(to='matches.MapChoice')), - ], - ), - migrations.AddField( - model_name='match', - name='map', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='match_map', to='matches.MapChoice'), - ), - ] diff --git a/matches/migrations/0008_match_type.py b/matches/migrations/0008_match_type.py deleted file mode 100644 index ce86353d6..000000000 --- a/matches/migrations/0008_match_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-24 00:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0007_auto_20190112_1514'), - ] - - operations = [ - migrations.AddField( - model_name='match', - name='type', - field=models.CharField(blank=True, max_length=20, null=True), - ), - ] diff --git a/matches/migrations/0009_auto_20190328_1906.py b/matches/migrations/0009_auto_20190328_1906.py deleted file mode 100644 index 1c420c898..000000000 --- a/matches/migrations/0009_auto_20190328_1906.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-28 23:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0008_auto_20190315_2152'), - ] - - operations = [ - migrations.AlterField( - model_name='mappoolchoice', - name='maps', - field=models.ManyToManyField(blank=True, null=True, to='matches.MapChoice'), - ), - ] diff --git a/matches/migrations/0010_auto_20190328_2021.py b/matches/migrations/0010_auto_20190328_2021.py deleted file mode 100644 index f32a00455..000000000 --- a/matches/migrations/0010_auto_20190328_2021.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 00:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0009_auto_20190328_1906'), - ] - - operations = [ - migrations.AlterField( - model_name='mapchoice', - name='map_num', - field=models.IntegerField(blank=True, default=0, null=True), - ), - ] diff --git a/matches/migrations/0011_auto_20190328_2021.py b/matches/migrations/0011_auto_20190328_2021.py deleted file mode 100644 index 13b983b0d..000000000 --- a/matches/migrations/0011_auto_20190328_2021.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 00:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0010_auto_20190328_2021'), - ] - - operations = [ - migrations.AlterField( - model_name='mapchoice', - name='map_num', - field=models.IntegerField(blank=True, default=0), - ), - ] diff --git a/matches/migrations/0012_auto_20190328_2021.py b/matches/migrations/0012_auto_20190328_2021.py deleted file mode 100644 index 3ea3d6ebb..000000000 --- a/matches/migrations/0012_auto_20190328_2021.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 00:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0011_auto_20190328_2021'), - ] - - operations = [ - migrations.AlterField( - model_name='mapchoice', - name='map_num', - field=models.IntegerField(blank=True, default=0, null=True), - ), - ] diff --git a/matches/migrations/0013_auto_20190328_2022.py b/matches/migrations/0013_auto_20190328_2022.py deleted file mode 100644 index 83b62c522..000000000 --- a/matches/migrations/0013_auto_20190328_2022.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 00:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0012_auto_20190328_2021'), - ] - - operations = [ - migrations.AlterField( - model_name='mappoolchoice', - name='maps', - field=models.ManyToManyField(blank=True, to='matches.MapChoice'), - ), - ] diff --git a/matches/migrations/0014_merge_20190605_1915.py b/matches/migrations/0014_merge_20190605_1915.py deleted file mode 100644 index c645b07f6..000000000 --- a/matches/migrations/0014_merge_20190605_1915.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-05 23:15 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0013_auto_20190328_2022'), - ('matches', '0008_match_type'), - ] - - operations = [ - ] diff --git a/matches/migrations/0015_auto_20190713_1335.py b/matches/migrations/0015_auto_20190713_1335.py deleted file mode 100644 index 9489e54fa..000000000 --- a/matches/migrations/0015_auto_20190713_1335.py +++ /dev/null @@ -1,100 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0014_merge_20190605_1915'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='awayteam', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='awayteam', to='teams.Team'), - ), - migrations.AlterField( - model_name='match', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='GameChoice', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='match', - name='hometeam', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hometeam', to='teams.Team'), - ), - migrations.AlterField( - model_name='match', - name='loser', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loser', to='teams.Team'), - ), - migrations.AlterField( - model_name='match', - name='map', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_map', to='matches.MapChoice'), - ), - migrations.AlterField( - model_name='match', - name='platform', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='PlatformChoice', to='matches.PlatformChoice'), - ), - migrations.AlterField( - model_name='match', - name='team1reportedwinner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1reportedwinner', to='teams.Team'), - ), - migrations.AlterField( - model_name='match', - name='team2reportedwinner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2reportedwinner', to='teams.Team'), - ), - migrations.AlterField( - model_name='match', - name='winner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='champions', to='teams.Team'), - ), - migrations.AlterField( - model_name='matchdispute', - name='team1', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1', to='teams.Team'), - ), - migrations.AlterField( - model_name='matchdispute', - name='team1origreporter', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1OriginalReporter', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='matchdispute', - name='team2', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2', to='teams.Team'), - ), - migrations.AlterField( - model_name='matchdispute', - name='team2origreporter', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2OriginalReporter', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='matchdispute', - name='teamreporter', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1Disputer', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='matchreport', - name='reported_winner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winnerreporting', to='teams.Team'), - ), - migrations.AlterField( - model_name='matchreport', - name='reporting_team', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teamreporting', to='teams.Team'), - ), - migrations.AlterField( - model_name='matchreport', - name='reporting_user', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='userreporting', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/matches/migrations/0016_auto_20200108_1654.py b/matches/migrations/0016_auto_20200108_1654.py deleted file mode 100644 index 9c07a0e1c..000000000 --- a/matches/migrations/0016_auto_20200108_1654.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.2 on 2020-01-08 21:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0015_auto_20190713_1335'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='bestof', - field=models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0, null=True), - ), - ] diff --git a/matches/migrations/0017_sportchoice.py b/matches/migrations/0017_sportchoice.py deleted file mode 100644 index f1f24e766..000000000 --- a/matches/migrations/0017_sportchoice.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.2 on 2020-01-08 22:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0016_auto_20200108_1654'), - ] - - operations = [ - migrations.CreateModel( - name='SportChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='unknown sports', max_length=255)), - ], - ), - ] diff --git a/matches/migrations/0018_match_disable_userreports.py b/matches/migrations/0018_match_disable_userreports.py deleted file mode 100644 index fb237a37a..000000000 --- a/matches/migrations/0018_match_disable_userreports.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0017_sportchoice'), - ] - - operations = [ - migrations.AddField( - model_name='match', - name='disable_userreports', - field=models.BooleanField(default=True), - ), - ] diff --git a/matches/migrations/0019_auto_20200115_1127.py b/matches/migrations/0019_auto_20200115_1127.py deleted file mode 100644 index f7714d954..000000000 --- a/matches/migrations/0019_auto_20200115_1127.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:27 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0018_match_disable_userreports'), - ] - - operations = [ - migrations.AddField( - model_name='match', - name='sports', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='SportChoice', to='matches.SportChoice'), - ), - migrations.AlterField( - model_name='match', - name='game', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='GameChoice', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='match', - name='platform', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='PlatformChoice', to='matches.PlatformChoice'), - ), - ] diff --git a/matches/migrations/0020_auto_20200115_1132.py b/matches/migrations/0020_auto_20200115_1132.py deleted file mode 100644 index 955652041..000000000 --- a/matches/migrations/0020_auto_20200115_1132.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0019_auto_20200115_1127'), - ] - - operations = [ - migrations.RenameField( - model_name='match', - old_name='sports', - new_name='sport', - ), - ] diff --git a/matches/migrations/0021_auto_20200115_1133.py b/matches/migrations/0021_auto_20200115_1133.py deleted file mode 100644 index adfcd1a67..000000000 --- a/matches/migrations/0021_auto_20200115_1133.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0020_auto_20200115_1132'), - ] - - operations = [ - migrations.RenameField( - model_name='match', - old_name='disable_userreports', - new_name='disable_userreport', - ), - ] diff --git a/matches/migrations/0022_auto_20200410_1506.py b/matches/migrations/0022_auto_20200410_1506.py deleted file mode 100644 index 14dffb599..000000000 --- a/matches/migrations/0022_auto_20200410_1506.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.3 on 2020-04-10 19:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0021_auto_20200115_1133'), - ] - - operations = [ - migrations.AlterField( - model_name='match', - name='bestof', - field=models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0), - ), - ] diff --git a/matches/migrations/0023_matchstats_statsplayer.py b/matches/migrations/0023_matchstats_statsplayer.py deleted file mode 100644 index 1711e405e..000000000 --- a/matches/migrations/0023_matchstats_statsplayer.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 2.2.14 on 2020-08-22 19:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0022_auto_20200410_1506'), - ] - - operations = [ - migrations.CreateModel( - name='MatchStats', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('matchid', models.PositiveIntegerField(default=0)), - ('map', models.CharField(default='unknown', max_length=255)), - ('team1', models.CharField(default='unknown', max_length=255)), - ('team2', models.CharField(default='unknown', max_length=255)), - ], - ), - migrations.CreateModel( - name='StatsPlayer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rating', models.DecimalField(decimal_places=3, max_digits=6)), - ('kills', models.IntegerField(default=0)), - ('assists', models.IntegerField(default=0)), - ('deaths', models.IntegerField(default=0)), - ('killround', models.DecimalField(decimal_places=3, max_digits=6)), - ('adr', models.IntegerField(default=0)), - ('ud', models.IntegerField(default=0)), - ('ef', models.IntegerField(default=0)), - ('f_assists', models.IntegerField(default=0)), - ('hs', models.IntegerField(default=0)), - ('kast', models.IntegerField(default=0)), - ('awp_k', models.IntegerField(default=0)), - ('twok', models.IntegerField(default=0)), - ('threek', models.IntegerField(default=0)), - ('fourk', models.IntegerField(default=0)), - ('fivek', models.IntegerField(default=0)), - ('one_v_one', models.IntegerField(default=0)), - ('one_v_two', models.IntegerField(default=0)), - ('one_v_three', models.IntegerField(default=0)), - ('one_v_four', models.IntegerField(default=0)), - ('one_v_five', models.IntegerField(default=0)), - ('f_kills', models.IntegerField(default=0)), - ('f_deaths', models.IntegerField(default=0)), - ('entries', models.IntegerField(default=0)), - ('trades', models.IntegerField(default=0)), - ('rounds', models.IntegerField(default=0)), - ('rf', models.IntegerField(default=0)), - ('ra', models.IntegerField(default=0)), - ('damage', models.IntegerField(default=0)), - ], - ), - ] diff --git a/matches/migrations/0024_matchcheckin_teammatchstats.py b/matches/migrations/0024_matchcheckin_teammatchstats.py deleted file mode 100644 index 24afbf70c..000000000 --- a/matches/migrations/0024_matchcheckin_teammatchstats.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.2.15 on 2020-11-13 16:06 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0008_team_image'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('matches', '0023_matchstats_statsplayer'), - ] - - operations = [ - migrations.CreateModel( - name='TeamMatchStats', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rounds_won', models.PositiveSmallIntegerField(default=0)), - ('rounds_lost', models.PositiveSmallIntegerField(default=0)), - ('total_kills', models.PositiveSmallIntegerField(default=0)), - ('total_deaths', models.PositiveSmallIntegerField(default=0)), - ], - ), - migrations.CreateModel( - name='MatchCheckIn', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('match', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_checkin', to='matches.Match')), - ('players', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), - ('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkin_user', to=settings.AUTH_USER_MODEL)), - ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checking_in_team', to='teams.Team')), - ], - ), - ] diff --git a/news/migrations/0001_initial.py b/news/migrations/0001_initial.py index c23029651..add29dff8 100644 --- a/news/migrations/0001_initial.py +++ b/news/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.2 on 2018-08-03 15:10 +# Generated by Django 2.2.15 on 2020-11-17 00:02 from django.conf import settings from django.db import migrations, models @@ -7,6 +7,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [ @@ -15,46 +16,37 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Comment', + name='Post', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=80)), - ('email', models.EmailField(max_length=254)), + ('title', models.CharField(max_length=250)), + ('slug', models.SlugField(max_length=250, unique_for_date='publish')), ('body', models.TextField()), + ('publish', models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True)), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('active', models.BooleanField(default=True)), + ('status', models.CharField(choices=[('draft', 'Draft'), ('scheduled', 'Scheduled'), ('published', 'Published'), ('private', 'Private')], default='draft', max_length=12)), + ('image', models.ImageField(blank=True, upload_to='news_images')), + ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blog_posts', to=settings.AUTH_USER_MODEL)), ], options={ - 'ordering': ('created',), + 'ordering': ('-publish',), }, ), migrations.CreateModel( - name='Post', + name='Comment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=250)), - ('slug', models.SlugField(max_length=250, unique_for_date='publish')), + ('name', models.CharField(max_length=80)), + ('email', models.EmailField(max_length=254)), ('body', models.TextField()), - ('publish', models.DateTimeField(blank=True, default=django.utils.timezone.now)), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('status', models.CharField( - choices=[('draft', 'Draft'), ('scheduled', 'Scheduled'), ('published', 'Published'), - ('private', 'Private')], default='draft', max_length=12)), - ('image', models.ImageField(blank=True, upload_to='news_images')), - ('author', - models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', - to=settings.AUTH_USER_MODEL)), + ('active', models.BooleanField(default=True)), + ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='news.Post')), ], options={ - 'ordering': ('-publish',), + 'ordering': ('created',), }, ), - migrations.AddField( - model_name='comment', - name='post', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', - to='news.Post'), - ), ] diff --git a/news/migrations/0002_auto_20190713_1335.py b/news/migrations/0002_auto_20190713_1335.py deleted file mode 100644 index 44d07fbe9..000000000 --- a/news/migrations/0002_auto_20190713_1335.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='post', - name='author', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blog_posts', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/news/migrations/0003_auto_20200507_1737.py b/news/migrations/0003_auto_20200507_1737.py deleted file mode 100644 index 12f807de0..000000000 --- a/news/migrations/0003_auto_20200507_1737.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-07 17:37 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0002_auto_20190713_1335'), - ] - - operations = [ - migrations.AlterField( - model_name='post', - name='publish', - field=models.DateTimeField(default=django.utils.timezone.now, null=True), - ), - ] diff --git a/news/migrations/0004_auto_20200507_1741.py b/news/migrations/0004_auto_20200507_1741.py deleted file mode 100644 index 995ab695f..000000000 --- a/news/migrations/0004_auto_20200507_1741.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-07 17:41 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0003_auto_20200507_1737'), - ] - - operations = [ - migrations.AlterField( - model_name='post', - name='publish', - field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True), - ), - ] diff --git a/pages/fixtures/pages.json b/pages/fixtures/pages.json deleted file mode 100644 index aa38aeeec..000000000 --- a/pages/fixtures/pages.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "model": "pages.socialinfo", - "pk": 1, - "fields": { - "twitchchannel": null, - "youtubechannel": null, - "twitterprofile": "http://twitter.com/NFM_Studios", - "facebookpage": null, - "instagrampage": null, - "stream": null - } - }, - { - "model": "pages.staticinfo", - "pk": 1, - "fields": { - "featured_tournament": null, - "about_us": "about us", - "terms": "terms of service", - "block1text": null, - "block1link": null, - "block1_img": "", - "block2text": null, - "block2link": null, - "block2_img": "", - "block3text": null, - "block3link": null, - "block3_img": "" - } - } -] \ No newline at end of file diff --git a/pages/migrations/0001_initial.py b/pages/migrations/0001_initial.py index 96186d6f3..5270c7623 100644 --- a/pages/migrations/0001_initial.py +++ b/pages/migrations/0001_initial.py @@ -1,37 +1,76 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:46 +# Generated by Django 2.2.15 on 2020-11-17 00:02 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True dependencies = [ + ('singletournaments', '__first__'), ] operations = [ + migrations.CreateModel( + name='FrontPageSlide', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('header', models.CharField(blank=True, default='', max_length=50, null=True)), + ('subhead', models.CharField(blank=True, default='', max_length=100, null=True)), + ('link', models.URLField(blank=True, null=True)), + ('image', models.ImageField(blank=True, upload_to='carousel_images')), + ], + options={ + 'verbose_name_plural': 'Front Page Slides', + }, + ), migrations.CreateModel( name='Partner', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=80)), - ('website', models.URLField(blank=True, default='#')), + ('website', models.URLField(blank=True, null=True)), ('twitter', models.CharField(blank=True, default='#', max_length=100)), ('bio', models.TextField()), ('logo', models.ImageField(blank=True, upload_to='partner_images')), ], ), + migrations.CreateModel( + name='SocialInfo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('twitchchannel', models.URLField(blank=True, null=True, verbose_name='twitch_channel')), + ('youtubechannel', models.URLField(blank=True, null=True, verbose_name='youtube_channel')), + ('twitterprofile', models.URLField(blank=True, null=True, verbose_name='twitter_profile')), + ('facebookpage', models.URLField(blank=True, null=True, verbose_name='facebook_page')), + ('instagrampage', models.URLField(blank=True, null=True, verbose_name='instagram_page')), + ('stream', models.CharField(blank=True, max_length=25, null=True)), + ], + options={ + 'verbose_name_plural': 'Social info', + }, + ), migrations.CreateModel( name='StaticInfo', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('about_us', models.TextField(default='about us')), ('terms', models.TextField(default='terms of service')), - ('privacy', models.TextField(default='privacy policy')), - ('stream', models.CharField(default='twitch', max_length=25)), - ('slide1link', models.TextField(default='#')), - ('slide2link', models.TextField(default='#')), - ('slide3link', models.TextField(default='#')), + ('block1text', models.CharField(blank=True, default='', max_length=50, null=True)), + ('block1link', models.URLField(blank=True, null=True)), + ('block1_img', models.ImageField(blank=True, upload_to='carousel_images')), + ('block2text', models.CharField(blank=True, default='', max_length=50, null=True)), + ('block2link', models.URLField(blank=True, null=True)), + ('block2_img', models.ImageField(blank=True, upload_to='carousel_images')), + ('block3text', models.CharField(blank=True, default='', max_length=50, null=True)), + ('block3link', models.URLField(blank=True, null=True)), + ('block3_img', models.ImageField(blank=True, upload_to='carousel_images')), + ('featured_tournament', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='featured_tournament', to='singletournaments.SingleEliminationTournament')), ], + options={ + 'verbose_name_plural': 'Static info', + }, ), ] diff --git a/pages/migrations/0002_auto_20180916_1705.py b/pages/migrations/0002_auto_20180916_1705.py deleted file mode 100644 index b9d9d2c2c..000000000 --- a/pages/migrations/0002_auto_20180916_1705.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.5 on 2018-09-16 21:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='welcomeln1', - field=models.CharField(default='welcome text', max_length=25), - ), - migrations.AddField( - model_name='staticinfo', - name='welcomeln2', - field=models.CharField(default='', max_length=25), - ), - ] diff --git a/pages/migrations/0003_auto_20180920_2018.py b/pages/migrations/0003_auto_20180920_2018.py deleted file mode 100644 index cb1763dc6..000000000 --- a/pages/migrations/0003_auto_20180920_2018.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.0.4 on 2018-09-21 00:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0002_auto_20180916_1705'), - ] - - operations = [ - migrations.RemoveField( - model_name='staticinfo', - name='privacy', - ), - migrations.AddField( - model_name='staticinfo', - name='slide1_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='slide2_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='slide3_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AlterField( - model_name='staticinfo', - name='welcomeln1', - field=models.CharField(default='welcome1', max_length=25), - ), - migrations.AlterField( - model_name='staticinfo', - name='welcomeln2', - field=models.CharField(default='welcome2', max_length=25), - ), - ] diff --git a/pages/migrations/0004_auto_20181008_1545.py b/pages/migrations/0004_auto_20181008_1545.py deleted file mode 100644 index 422eb2157..000000000 --- a/pages/migrations/0004_auto_20181008_1545.py +++ /dev/null @@ -1,87 +0,0 @@ -# Generated by Django 2.0.5 on 2018-10-08 19:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0003_auto_20180920_2018'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='bingeslide1big', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide1link', - field=models.URLField(default='#'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide1small', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide2big', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide2link', - field=models.URLField(default='#'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide2small', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide3big', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide3link', - field=models.URLField(default='#'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingeslide3small', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop1', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop1link', - field=models.URLField(default='#'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop2', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop2link', - field=models.URLField(default='#'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop3', - field=models.TextField(default='Coming Soon!'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop3link', - field=models.URLField(default='#'), - ), - ] diff --git a/pages/migrations/0005_auto_20181012_1849.py b/pages/migrations/0005_auto_20181012_1849.py deleted file mode 100644 index 798d3d729..000000000 --- a/pages/migrations/0005_auto_20181012_1849.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.0.4 on 2018-10-12 22:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0004_auto_20181008_1545'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='bingetop1image', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop2image', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop3image', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - ] diff --git a/pages/migrations/0006_auto_20181012_1953.py b/pages/migrations/0006_auto_20181012_1953.py deleted file mode 100644 index 09f15ff65..000000000 --- a/pages/migrations/0006_auto_20181012_1953.py +++ /dev/null @@ -1,102 +0,0 @@ -# Generated by Django 2.0.4 on 2018-10-12 23:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0005_auto_20181012_1849'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='bingeslide1big', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide1link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide1small', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide2big', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide2link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide2small', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide3big', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide3link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide3small', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop1', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop1link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop2', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop2link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop3', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop3link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide1link', - field=models.TextField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide2link', - field=models.TextField(blank=True, default='#', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide3link', - field=models.TextField(blank=True, default='#', null=True), - ), - ] diff --git a/pages/migrations/0007_auto_20190206_1617.py b/pages/migrations/0007_auto_20190206_1617.py deleted file mode 100644 index 8d2c4a906..000000000 --- a/pages/migrations/0007_auto_20190206_1617.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.0.10 on 2019-02-06 21:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0006_auto_20181012_1953'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='facebookpage', - field=models.URLField(default='#', verbose_name='facebook_page'), - ), - migrations.AddField( - model_name='staticinfo', - name='instagrampage', - field=models.URLField(default='#', verbose_name='instagram_page'), - ), - migrations.AddField( - model_name='staticinfo', - name='twitchchannel', - field=models.URLField(default='#', verbose_name='twitch_channel'), - ), - migrations.AddField( - model_name='staticinfo', - name='twitterprofile', - field=models.URLField(default='#', verbose_name='twitter_profile'), - ), - migrations.AddField( - model_name='staticinfo', - name='youtubechannel', - field=models.URLField(default='#', verbose_name='youtube_channel'), - ), - ] diff --git a/pages/migrations/0008_auto_20190206_1624.py b/pages/migrations/0008_auto_20190206_1624.py deleted file mode 100644 index 5d31ca641..000000000 --- a/pages/migrations/0008_auto_20190206_1624.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.0.10 on 2019-02-06 21:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0007_auto_20190206_1617'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='facebookpage', - field=models.URLField(blank=True, null=True, verbose_name='facebook_page'), - ), - migrations.AlterField( - model_name='staticinfo', - name='instagrampage', - field=models.URLField(blank=True, null=True, verbose_name='instagram_page'), - ), - migrations.AlterField( - model_name='staticinfo', - name='twitchchannel', - field=models.URLField(blank=True, null=True, verbose_name='twitch_channel'), - ), - migrations.AlterField( - model_name='staticinfo', - name='twitterprofile', - field=models.URLField(blank=True, null=True, verbose_name='twitter_profile'), - ), - migrations.AlterField( - model_name='staticinfo', - name='youtubechannel', - field=models.URLField(blank=True, null=True, verbose_name='youtube_channel'), - ), - ] diff --git a/pages/migrations/0009_auto_20190206_1628.py b/pages/migrations/0009_auto_20190206_1628.py deleted file mode 100644 index 029458a62..000000000 --- a/pages/migrations/0009_auto_20190206_1628.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 2.0.10 on 2019-02-06 21:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0008_auto_20190206_1624'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='facebookpage', - field=models.URLField(blank=True, default='https://www.facebook.com', null=True, - verbose_name='facebook_page'), - ), - migrations.AlterField( - model_name='staticinfo', - name='instagrampage', - field=models.URLField(blank=True, default='https://www.instagram.com', null=True, - verbose_name='instagram_page'), - ), - migrations.AlterField( - model_name='staticinfo', - name='twitchchannel', - field=models.URLField(blank=True, default='https://www.twitch.tv', null=True, - verbose_name='twitch_channel'), - ), - migrations.AlterField( - model_name='staticinfo', - name='twitterprofile', - field=models.URLField(blank=True, default='https://www.twitter.com', null=True, - verbose_name='twitter_profile'), - ), - migrations.AlterField( - model_name='staticinfo', - name='youtubechannel', - field=models.URLField(blank=True, default='https://www.youtube.com', null=True, - verbose_name='youtube_channel'), - ), - ] diff --git a/pages/migrations/0010_auto_20190207_1944.py b/pages/migrations/0010_auto_20190207_1944.py deleted file mode 100644 index def6055e1..000000000 --- a/pages/migrations/0010_auto_20190207_1944.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-08 00:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0009_auto_20190206_1628'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='welcomeln1', - field=models.CharField(blank=True, default='welcome1', max_length=25, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='welcomeln2', - field=models.CharField(blank=True, default='welcome2', max_length=25, null=True), - ), - ] diff --git a/pages/migrations/0011_auto_20190217_1554.py b/pages/migrations/0011_auto_20190217_1554.py deleted file mode 100644 index 9d051bdf8..000000000 --- a/pages/migrations/0011_auto_20190217_1554.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-17 20:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0010_auto_20190207_1944'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='bingetop1linktxt', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop2linktxt', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop3linktxt', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop4', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop4image', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop4link', - field=models.URLField(blank=True, default='#', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='bingetop4linktxt', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - ] diff --git a/pages/migrations/0012_auto_20190312_1956.py b/pages/migrations/0012_auto_20190312_1956.py deleted file mode 100644 index 9eddc3f0a..000000000 --- a/pages/migrations/0012_auto_20190312_1956.py +++ /dev/null @@ -1,63 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-12 23:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0011_auto_20190217_1554'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='bingeslide1link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide2link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingeslide3link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop1link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop2link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop3link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='bingetop4link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide1link', - field=models.TextField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide2link', - field=models.TextField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide3link', - field=models.TextField(blank=True, default='https://www.google.com', null=True), - ), - ] diff --git a/pages/migrations/0013_auto_20190313_1311.py b/pages/migrations/0013_auto_20190313_1311.py deleted file mode 100644 index 1cc0ac9ba..000000000 --- a/pages/migrations/0013_auto_20190313_1311.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-13 17:11 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0012_auto_20190112_1546'), - ('pages', '0012_auto_20190312_1956'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='featured_touranment', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='featured_tournamet', to='singletournaments.SingleEliminationTournament'), - ), - migrations.AlterField( - model_name='partner', - name='website', - field=models.URLField(blank=True, default='https://www.google.com'), - ), - ] diff --git a/pages/migrations/0014_remove_staticinfo_featured_touranment.py b/pages/migrations/0014_remove_staticinfo_featured_touranment.py deleted file mode 100644 index 0e300ee2d..000000000 --- a/pages/migrations/0014_remove_staticinfo_featured_touranment.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-13 17:22 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0013_auto_20190313_1311'), - ] - - operations = [ - migrations.RemoveField( - model_name='staticinfo', - name='featured_touranment', - ), - ] diff --git a/pages/migrations/0015_staticinfo_featured_touranment.py b/pages/migrations/0015_staticinfo_featured_touranment.py deleted file mode 100644 index 163b20f76..000000000 --- a/pages/migrations/0015_staticinfo_featured_touranment.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-13 17:23 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0012_auto_20190112_1546'), - ('pages', '0014_remove_staticinfo_featured_touranment'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='featured_touranment', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='featured_tournamet', to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0016_auto_20190327_1727.py b/pages/migrations/0016_auto_20190327_1727.py deleted file mode 100644 index 1b7228d7e..000000000 --- a/pages/migrations/0016_auto_20190327_1727.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-27 21:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0015_staticinfo_featured_touranment'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='gatournament1', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournament1big', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournament1small', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournament2', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournament2big', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournament2small', - field=models.TextField(blank=True, default='Coming Soon!', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='gatournamentbig', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - ] diff --git a/pages/migrations/0017_auto_20190328_1523.py b/pages/migrations/0017_auto_20190328_1523.py deleted file mode 100644 index d92de173c..000000000 --- a/pages/migrations/0017_auto_20190328_1523.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-28 19:23 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0013_auto_20190313_1400'), - ('pages', '0016_auto_20190327_1727'), - ] - - operations = [ - migrations.RemoveField( - model_name='staticinfo', - name='featured_touranment', - ), - migrations.AddField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='featured_tournament', to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0018_auto_20190329_1930.py b/pages/migrations/0018_auto_20190329_1930.py deleted file mode 100644 index f1ee36b9c..000000000 --- a/pages/migrations/0018_auto_20190329_1930.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 23:30 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0017_auto_20190328_1523'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='featured_tournament', to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0018_auto_20190426_1727.py b/pages/migrations/0018_auto_20190426_1727.py deleted file mode 100644 index 0a85f428e..000000000 --- a/pages/migrations/0018_auto_20190426_1727.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.5 on 2019-04-26 21:27 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0017_auto_20190328_1523'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='featured_tournament', - to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0019_auto_20190523_2257.py b/pages/migrations/0019_auto_20190523_2257.py deleted file mode 100644 index 7be9959eb..000000000 --- a/pages/migrations/0019_auto_20190523_2257.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-24 02:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0018_auto_20190426_1727'), - ] - - operations = [ - migrations.CreateModel( - name='SocialInfo', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('twitchchannel', models.URLField(blank=True, default='https://www.twitch.tv', null=True, verbose_name='twitch_channel')), - ('youtubechannel', models.URLField(blank=True, default='https://www.youtube.com', null=True, verbose_name='youtube_channel')), - ('twitterprofile', models.URLField(blank=True, default='https://www.twitter.com', null=True, verbose_name='twitter_profile')), - ('facebookpage', models.URLField(blank=True, default='https://www.facebook.com', null=True, verbose_name='facebook_page')), - ('instagrampage', models.URLField(blank=True, default='https://www.instagram.com', null=True, verbose_name='instagram_page')), - ('stream', models.CharField(default='twitch', max_length=25)), - ], - ), - migrations.RemoveField( - model_name='staticinfo', - name='facebookpage', - ), - migrations.RemoveField( - model_name='staticinfo', - name='instagrampage', - ), - migrations.RemoveField( - model_name='staticinfo', - name='stream', - ), - migrations.RemoveField( - model_name='staticinfo', - name='twitchchannel', - ), - migrations.RemoveField( - model_name='staticinfo', - name='twitterprofile', - ), - migrations.RemoveField( - model_name='staticinfo', - name='youtubechannel', - ), - ] diff --git a/pages/migrations/0019_merge_20190605_1915.py b/pages/migrations/0019_merge_20190605_1915.py deleted file mode 100644 index b38906dd6..000000000 --- a/pages/migrations/0019_merge_20190605_1915.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-05 23:15 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0018_auto_20190426_1727'), - ('pages', '0018_auto_20190329_1930'), - ] - - operations = [ - ] diff --git a/pages/migrations/0020_auto_20190605_1916.py b/pages/migrations/0020_auto_20190605_1916.py deleted file mode 100644 index 12d1a6940..000000000 --- a/pages/migrations/0020_auto_20190605_1916.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-05 23:16 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0019_merge_20190605_1915'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='featured_tournament', to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0021_merge_20190607_1511.py b/pages/migrations/0021_merge_20190607_1511.py deleted file mode 100644 index 25a9ccacb..000000000 --- a/pages/migrations/0021_merge_20190607_1511.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-07 19:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0020_auto_20190605_1916'), - ('pages', '0019_auto_20190523_2257'), - ] - - operations = [ - ] diff --git a/pages/migrations/0022_auto_20190620_1748.py b/pages/migrations/0022_auto_20190620_1748.py deleted file mode 100644 index 7c06c33b3..000000000 --- a/pages/migrations/0022_auto_20190620_1748.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-20 21:48 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0021_merge_20190607_1511'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='featured_tournament', - to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0023_auto_20190620_1749.py b/pages/migrations/0023_auto_20190620_1749.py deleted file mode 100644 index acbc724aa..000000000 --- a/pages/migrations/0023_auto_20190620_1749.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-20 21:49 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('pages', '0022_auto_20190620_1748'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='featured_tournament', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='featured_tournament', - to='singletournaments.SingleEliminationTournament'), - ), - ] diff --git a/pages/migrations/0024_auto_20200108_1654.py b/pages/migrations/0024_auto_20200108_1654.py deleted file mode 100644 index 3138a7919..000000000 --- a/pages/migrations/0024_auto_20200108_1654.py +++ /dev/null @@ -1,141 +0,0 @@ -# Generated by Django 2.2.2 on 2020-01-08 21:54 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0023_auto_20190620_1749'), - ] - - operations = [ - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide1big', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide1link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide1small', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide2big', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide2link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide2small', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide3big', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide3link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingeslide3small', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop1', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop1image', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop1link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop1linktxt', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop2', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop2image', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop2link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop2linktxt', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop3', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop3image', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop3link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop3linktxt', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop4', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop4image', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop4link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='bingetop4linktxt', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament1', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament1big', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament1small', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament2', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament2big', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournament2small', - ), - migrations.RemoveField( - model_name='staticinfo', - name='gatournamentbig', - ), - ] diff --git a/pages/migrations/0025_auto_20200410_1506.py b/pages/migrations/0025_auto_20200410_1506.py deleted file mode 100644 index f096cf19c..000000000 --- a/pages/migrations/0025_auto_20200410_1506.py +++ /dev/null @@ -1,88 +0,0 @@ -# Generated by Django 2.2.3 on 2020-04-10 19:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0024_auto_20200108_1654'), - ] - - operations = [ - migrations.AddField( - model_name='staticinfo', - name='block1_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='block1link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='block1text', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='block2_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='block2link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='block2text', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='block3_img', - field=models.ImageField(blank=True, upload_to='carousel_images'), - ), - migrations.AddField( - model_name='staticinfo', - name='block3link', - field=models.URLField(blank=True, default='https://www.google.com', null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='block3text', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide1header', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide1subhead', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide2header', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide2subhead', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide3header', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - migrations.AddField( - model_name='staticinfo', - name='slide3subhead', - field=models.CharField(blank=True, default='', max_length=50, null=True), - ), - ] diff --git a/pages/migrations/0026_auto_20200410_1525.py b/pages/migrations/0026_auto_20200410_1525.py deleted file mode 100644 index 3850ea8f8..000000000 --- a/pages/migrations/0026_auto_20200410_1525.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 2.2.3 on 2020-04-10 19:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0025_auto_20200410_1506'), - ] - - operations = [ - migrations.AlterModelOptions( - name='socialinfo', - options={'verbose_name_plural': 'Social info'}, - ), - migrations.AlterModelOptions( - name='staticinfo', - options={'verbose_name_plural': 'Static info'}, - ), - migrations.AlterField( - model_name='socialinfo', - name='facebookpage', - field=models.URLField(blank=True, null=True, verbose_name='facebook_page'), - ), - migrations.AlterField( - model_name='socialinfo', - name='instagrampage', - field=models.URLField(blank=True, null=True, verbose_name='instagram_page'), - ), - migrations.AlterField( - model_name='socialinfo', - name='twitchchannel', - field=models.URLField(blank=True, null=True, verbose_name='twitch_channel'), - ), - migrations.AlterField( - model_name='socialinfo', - name='twitterprofile', - field=models.URLField(blank=True, null=True, verbose_name='twitter_profile'), - ), - migrations.AlterField( - model_name='socialinfo', - name='youtubechannel', - field=models.URLField(blank=True, null=True, verbose_name='youtube_channel'), - ), - ] diff --git a/pages/migrations/0027_auto_20200410_1526.py b/pages/migrations/0027_auto_20200410_1526.py deleted file mode 100644 index ff8c02b14..000000000 --- a/pages/migrations/0027_auto_20200410_1526.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 2.2.3 on 2020-04-10 19:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0026_auto_20200410_1525'), - ] - - operations = [ - migrations.AlterField( - model_name='partner', - name='website', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='socialinfo', - name='stream', - field=models.CharField(blank=True, max_length=25, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='block1link', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='block2link', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='block3link', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide1link', - field=models.TextField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide2link', - field=models.TextField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide3link', - field=models.TextField(blank=True, null=True), - ), - ] diff --git a/pages/migrations/0028_auto_20200410_1534.py b/pages/migrations/0028_auto_20200410_1534.py deleted file mode 100644 index f904c4ace..000000000 --- a/pages/migrations/0028_auto_20200410_1534.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.3 on 2020-04-10 19:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0027_auto_20200410_1526'), - ] - - operations = [ - migrations.AlterField( - model_name='staticinfo', - name='slide1link', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide2link', - field=models.URLField(blank=True, null=True), - ), - migrations.AlterField( - model_name='staticinfo', - name='slide3link', - field=models.URLField(blank=True, null=True), - ), - ] diff --git a/pages/migrations/0029_auto_20200420_1350.py b/pages/migrations/0029_auto_20200420_1350.py deleted file mode 100644 index b6c73e475..000000000 --- a/pages/migrations/0029_auto_20200420_1350.py +++ /dev/null @@ -1,82 +0,0 @@ -# Generated by Django 2.2.12 on 2020-04-20 17:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0028_auto_20200410_1534'), - ] - - operations = [ - migrations.CreateModel( - name='FrontPageSlide', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('header', models.CharField(blank=True, default='', max_length=50, null=True)), - ('subhead', models.CharField(blank=True, default='', max_length=100, null=True)), - ('link', models.URLField(blank=True, null=True)), - ('image', models.ImageField(blank=True, upload_to='carousel_images')), - ], - options={ - 'verbose_name_plural': 'Front Page Slides', - }, - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide1_img', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide1header', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide1link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide1subhead', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide2_img', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide2header', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide2link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide2subhead', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide3_img', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide3header', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide3link', - ), - migrations.RemoveField( - model_name='staticinfo', - name='slide3subhead', - ), - migrations.RemoveField( - model_name='staticinfo', - name='welcomeln1', - ), - migrations.RemoveField( - model_name='staticinfo', - name='welcomeln2', - ), - ] diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py index 8200b78dd..2b791a886 100644 --- a/profiles/migrations/0001_initial.py +++ b/profiles/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:46 +# Generated by Django 2.2.15 on 2020-11-17 00:02 from django.conf import settings from django.db import migrations, models @@ -7,6 +7,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [ @@ -14,29 +15,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='BannedUser', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ip', models.CharField(default='error', max_length=12)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='banned', - to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='UserGear', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ownpc', models.BooleanField(default=False)), - ('cpu', models.CharField(default='No CPU specified', max_length=30)), - ('gpu', models.CharField(default='No GPU specified', max_length=30)), - ('psu', models.CharField(default='No PSU specified', max_length=30)), - ('case', models.CharField(default='No Case specified', max_length=30)), - ('os', models.CharField(default='No OS specified', max_length=30)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userspecs', - to=settings.AUTH_USER_MODEL)), - ], - ), migrations.CreateModel( name='UserProfile', fields=[ @@ -50,29 +28,43 @@ class Migration(migrations.Migration): ('xbl', models.CharField(blank=True, default='No Xbox Live Linked', max_length=30)), ('psn', models.CharField(blank=True, default='No PSN Linked', max_length=30)), ('steam', models.CharField(blank=True, default='No Steam Linked', max_length=30)), + ('epic', models.CharField(blank=True, default='No Epic Linked', max_length=30)), ('lol', models.CharField(blank=True, default='No LOL Linked', max_length=30)), ('battlenet', models.CharField(blank=True, default='No Battle.net Linked', max_length=30)), + ('activisionid', models.CharField(blank=True, default='No Activision ID Linked', max_length=30)), ('twitter_profile', models.CharField(blank=True, default='No Twitter Linked', max_length=30)), ('twitch_channel', models.CharField(blank=True, default='No Twitch Linked', max_length=50)), ('favorite_game', models.CharField(blank=True, default='N/A', max_length=50)), ('favorite_console', models.CharField(blank=True, default='N/A', max_length=50)), ('profile_picture', models.ImageField(blank=True, upload_to='profile_images')), - ('user_type', models.CharField(default='user', max_length=10)), - ('ip', models.CharField(default='0.0.0.0', max_length=16)), + ('user_type', models.CharField(choices=[('user', 'Standard User'), ('admin', 'Admin'), ('superadmin', 'Super Admin')], default='user', max_length=10)), + ('ip', models.CharField(default='0.0.0.0', max_length=45)), ('num_trophies', models.PositiveSmallIntegerField(default=0)), - ('xbl_verified', models.BooleanField(default=False)), + ('xbl_verified', models.BooleanField(blank=True, default=False)), ('psn_verified', models.BooleanField(default=False)), + ('user_verified', models.BooleanField(blank=True, default=False)), ('num_bronze', models.PositiveSmallIntegerField(default=0)), ('num_silver', models.PositiveSmallIntegerField(default=0)), ('num_gold', models.PositiveSmallIntegerField(default=0)), ('num_plat', models.PositiveSmallIntegerField(default=0)), ('num_diamond', models.PositiveSmallIntegerField(default=0)), ('num_titanium', models.PositiveSmallIntegerField(default=0)), + ('num_wagerwin', models.PositiveIntegerField(default=0)), + ('num_wagerloss', models.PositiveIntegerField(default=0)), ('tournament_wins', models.PositiveSmallIntegerField(default=0)), ('dubl_tournament_wins', models.PositiveSmallIntegerField(default=0)), + ('rank', models.PositiveSmallIntegerField(default=100)), ('country', django_countries.fields.CountryField(default='US', max_length=2)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', - to=settings.AUTH_USER_MODEL)), + ('email_enabled', models.BooleanField(default=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='BannedUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ip', models.CharField(default='error', max_length=12)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='banned', to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/profiles/migrations/0002_auto_20180803_1110.py b/profiles/migrations/0002_auto_20180803_1110.py deleted file mode 100644 index 6eeb7c9fb..000000000 --- a/profiles/migrations/0002_auto_20180803_1110.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.2 on 2018-08-03 15:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='ip', - field=models.CharField(default='0.0.0.0', max_length=45), - ), - ] diff --git a/profiles/migrations/0003_userprofile_rank.py b/profiles/migrations/0003_userprofile_rank.py deleted file mode 100644 index 0e4ce85bf..000000000 --- a/profiles/migrations/0003_userprofile_rank.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.1 on 2018-09-14 18:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0002_auto_20180803_1110'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='rank', - field=models.PositiveSmallIntegerField(default=100), - ), - ] diff --git a/profiles/migrations/0004_userprofile_user_verified.py b/profiles/migrations/0004_userprofile_user_verified.py deleted file mode 100644 index 3130b132a..000000000 --- a/profiles/migrations/0004_userprofile_user_verified.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-08 19:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0003_userprofile_rank'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='user_verified', - field=models.BooleanField(default=False), - ), - ] diff --git a/profiles/migrations/0005_userprofile_epic.py b/profiles/migrations/0005_userprofile_epic.py deleted file mode 100644 index 050c8eda9..000000000 --- a/profiles/migrations/0005_userprofile_epic.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.5 on 2018-10-13 20:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0004_userprofile_user_verified'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='epic', - field=models.CharField(blank=True, default='No Epic Linked', max_length=30), - ), - ] diff --git a/profiles/migrations/0006_auto_20190207_1944.py b/profiles/migrations/0006_auto_20190207_1944.py deleted file mode 100644 index c1bffb912..000000000 --- a/profiles/migrations/0006_auto_20190207_1944.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-08 00:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0005_userprofile_epic'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='user_verified', - field=models.BooleanField(blank=True, default=False), - ), - migrations.AlterField( - model_name='userprofile', - name='xbl_verified', - field=models.BooleanField(blank=True, default=False), - ), - ] diff --git a/profiles/migrations/0007_userprofile_email_enabled.py b/profiles/migrations/0007_userprofile_email_enabled.py deleted file mode 100644 index 401f6d61e..000000000 --- a/profiles/migrations/0007_userprofile_email_enabled.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-10 20:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0006_auto_20190207_1944'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='email_enabled', - field=models.BooleanField(default=True), - ), - ] diff --git a/profiles/migrations/0008_auto_20190602_1757.py b/profiles/migrations/0008_auto_20190602_1757.py deleted file mode 100644 index 90d8b6740..000000000 --- a/profiles/migrations/0008_auto_20190602_1757.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-06-02 21:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0007_userprofile_email_enabled'), - ] - - operations = [ - migrations.AlterField( - model_name='userprofile', - name='user_type', - field=models.CharField(choices=[('user', 'Standard User'), ('admin', 'Admin'), ('superadmin', 'Super Admin')], default='user', max_length=10), - ), - ] diff --git a/profiles/migrations/0009_auto_20190620_1725.py b/profiles/migrations/0009_auto_20190620_1725.py deleted file mode 100644 index b507a222c..000000000 --- a/profiles/migrations/0009_auto_20190620_1725.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-20 21:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('profiles', '0008_auto_20190602_1757'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='num_wagerloss', - field=models.PositiveIntegerField(default=0), - ), - migrations.AddField( - model_name='userprofile', - name='num_wagerwin', - field=models.PositiveIntegerField(default=0), - ), - ] diff --git a/profiles/migrations/0010_delete_usergear.py b/profiles/migrations/0010_delete_usergear.py deleted file mode 100644 index 134da3b3c..000000000 --- a/profiles/migrations/0010_delete_usergear.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-07 18:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0009_auto_20190620_1725'), - ] - - operations = [ - migrations.DeleteModel( - name='UserGear', - ), - ] diff --git a/profiles/migrations/0011_userprofile_activisionid.py b/profiles/migrations/0011_userprofile_activisionid.py deleted file mode 100644 index 07ccc86f9..000000000 --- a/profiles/migrations/0011_userprofile_activisionid.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-07 18:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0010_delete_usergear'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='activisionid', - field=models.CharField(blank=True, default='No Activision ID Linked', max_length=30), - ), - ] diff --git a/singletournaments/migrations/0001_initial.py b/singletournaments/migrations/0001_initial.py index baa49fdb0..e524a108a 100644 --- a/singletournaments/migrations/0001_initial.py +++ b/singletournaments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:46 +# Generated by Django 2.2.15 on 2020-11-17 00:03 from django.conf import settings from django.db import migrations, models @@ -6,12 +6,13 @@ class Migration(migrations.Migration): + initial = True dependencies = [ - ('matches', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('teams', '0001_initial'), + ('matches', '0001_initial'), ] operations = [ @@ -20,48 +21,39 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(default='No name provided', max_length=50, unique=True)), - ('teamformat', models.SmallIntegerField( - choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), - ('bestof', models.SmallIntegerField( - choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], - default=0)), + ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), + ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), ('active', models.BooleanField(default=False)), + ('twitch', models.CharField(default='twitch', max_length=60)), ('open_register', models.DateTimeField()), ('close_register', models.DateTimeField()), + ('allow_register', models.BooleanField(default=False)), ('info', models.TextField(default='No information provided')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('req_credits', models.PositiveSmallIntegerField(default=0)), - ('platform', models.SmallIntegerField( - choices=[(0, 'Playstation 4'), (1, 'Xbox One'), (2, 'PC'), (3, 'Mobile'), (4, 'Nintendo Switch'), - (5, 'Playstation 3'), (6, 'Xbox 360')], default=0)), - ('game', models.SmallIntegerField( - choices=[(0, 'No Game Set'), (1, 'Call of Duty Black Ops 3'), (2, 'Call of Duty WWII'), - (3, 'Fortnite'), (4, 'Destiny 2'), (5, 'Counter-Strike: Global Offensive'), - (6, 'Player Unknowns Battlegrounds'), (7, 'Rainbow Six Siege'), (8, 'Overwatch'), - (9, 'League of Legends'), (10, 'Hearthstone'), (11, 'World of Warcraft'), (12, 'Smite'), - (13, 'Rocket League'), (14, 'Battlefield 1')], default=0)), ('start', models.DateTimeField()), ('current_round', models.SmallIntegerField(blank=True, default=1)), - ('size', - models.PositiveSmallIntegerField(choices=[(4, 4), (8, 8), (16, 16), (32, 32), (64, 64), (128, 128)], - default=32)), + ('size', models.PositiveSmallIntegerField(choices=[(4, 4), (8, 8), (16, 16), (32, 32), (64, 64), (128, 128)], default=32)), + ('xp_seed', models.BooleanField(default=False)), ('bracket_generated', models.BooleanField(default=False)), ('prize1', models.CharField(default='no prize specified', max_length=50)), ('prize2', models.CharField(default='no prize specified', max_length=50)), ('prize3', models.CharField(default='no prize specified', max_length=50)), + ('image', models.ImageField(blank=True, upload_to='tournament_images')), + ('disable_userreport', models.BooleanField(default=True)), + ('game', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='game', to='matches.GameChoice')), + ('map_pool', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='map_pool', to='matches.MapPoolChoice')), + ('platform', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='matches.PlatformChoice')), ], ), migrations.CreateModel( - name='SingleTournamentRound', + name='SingleTournamentTeam', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('roundnum', models.PositiveSmallIntegerField(default=1)), - ('matchesnum', models.PositiveSmallIntegerField(default=2)), - ('matches', models.ManyToManyField(to='matches.Match')), - ('tournament', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='withtournamentround', - to='singletournaments.SingleEliminationTournament')), + ('seed', models.PositiveIntegerField(blank=True, default=0, null=True)), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='actualteam', to='teams.Team')), + ('tournament', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='intournament', to='singletournaments.SingleEliminationTournament')), ], ), migrations.CreateModel( @@ -72,45 +64,48 @@ class Migration(migrations.Migration): ('updated', models.DateTimeField(auto_now=True)), ('text', models.TextField()), ('name', models.CharField(max_length=25)), - ('creator', - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rulesetCreator', - to=settings.AUTH_USER_MODEL)), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rulesetCreator', to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( - name='SingleTournamentTeam', + name='SingleTournamentRound', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('seed', models.PositiveIntegerField(blank=True, default=0, null=True)), - ('team', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='actualteam', - to='teams.Team')), - ('tournament', - models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='intournament', - to='singletournaments.SingleEliminationTournament')), + ('roundnum', models.PositiveSmallIntegerField(default=1)), + ('matchesnum', models.PositiveSmallIntegerField(default=2)), + ('info', models.TextField(default='No info specified')), + ('matches', models.ManyToManyField(to='matches.Match')), + ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='withtournamentround', to='singletournaments.SingleEliminationTournament')), ], ), migrations.AddField( model_name='singleeliminationtournament', name='ruleset', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='tournamentruleset', to='singletournaments.SingleTournamentRuleset'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tournamentruleset', to='singletournaments.SingleTournamentRuleset'), ), migrations.AddField( model_name='singleeliminationtournament', name='second', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='secondplaceteam', to='teams.Team'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='secondplaceteam', to='teams.Team'), + ), + migrations.AddField( + model_name='singleeliminationtournament', + name='sport', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sport', to='matches.SportChoice'), ), migrations.AddField( model_name='singleeliminationtournament', name='teams', field=models.ManyToManyField(blank=True, to='teams.Team'), ), + migrations.AddField( + model_name='singleeliminationtournament', + name='third', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='thirdplaceteam', to='teams.Team'), + ), migrations.AddField( model_name='singleeliminationtournament', name='winner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='winningteam', to='teams.Team'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winningteam', to='teams.Team'), ), ] diff --git a/singletournaments/migrations/0002_singleeliminationtournament_third.py b/singletournaments/migrations/0002_singleeliminationtournament_third.py deleted file mode 100644 index d18142ec9..000000000 --- a/singletournaments/migrations/0002_singleeliminationtournament_third.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.0.7 on 2018-07-08 01:30 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0001_initial'), - ('singletournaments', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='third', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='thirdplaceteam', to='teams.Team'), - ), - ] diff --git a/singletournaments/migrations/0003_auto_20180914_1431.py b/singletournaments/migrations/0003_auto_20180914_1431.py deleted file mode 100644 index fa424d15d..000000000 --- a/singletournaments/migrations/0003_auto_20180914_1431.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.1 on 2018-09-14 18:31 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0002_singleeliminationtournament_third'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='ruleset', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='tournamentruleset', to='singletournaments.SingleTournamentRuleset'), - ), - ] diff --git a/singletournaments/migrations/0004_singleeliminationtournament_image.py b/singletournaments/migrations/0004_singleeliminationtournament_image.py deleted file mode 100644 index cea60f32d..000000000 --- a/singletournaments/migrations/0004_singleeliminationtournament_image.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.4 on 2018-10-07 19:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0003_auto_20180914_1431'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='image', - field=models.ImageField(blank=True, upload_to='tournament_images'), - ), - ] diff --git a/singletournaments/migrations/0004_singleeliminationtournament_xp_seed.py b/singletournaments/migrations/0004_singleeliminationtournament_xp_seed.py deleted file mode 100644 index 073e407cd..000000000 --- a/singletournaments/migrations/0004_singleeliminationtournament_xp_seed.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-11-05 03:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0003_auto_20180914_1431'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='xp_seed', - field=models.BooleanField(default=False), - ), - ] diff --git a/singletournaments/migrations/0005_auto_20181012_0254.py b/singletournaments/migrations/0005_auto_20181012_0254.py deleted file mode 100644 index a0a5618a4..000000000 --- a/singletournaments/migrations/0005_auto_20181012_0254.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-12 02:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0004_singleeliminationtournament_image'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.SmallIntegerField( - choices=[(0, 'No Game Set'), (1, 'Call of Duty Black Ops 3'), (2, 'Call of Duty WWII'), (3, 'Fortnite'), - (4, 'Destiny 2'), (5, 'Counter-Strike: Global Offensive'), - (6, 'Player Unknowns Battlegrounds'), (7, 'Rainbow Six Siege'), (8, 'Overwatch'), - (9, 'League of Legends'), (10, 'Hearthstone'), (11, 'World of Warcraft'), (12, 'Smite'), - (13, 'Rocket League'), (14, 'Battlefield 1'), (15, 'Black Ops 4')], default=0), - ), - ] diff --git a/singletournaments/migrations/0006_auto_20181018_2054.py b/singletournaments/migrations/0006_auto_20181018_2054.py deleted file mode 100644 index a8461eba7..000000000 --- a/singletournaments/migrations/0006_auto_20181018_2054.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.4 on 2018-10-19 00:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0005_auto_20181012_0254'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.SmallIntegerField( - choices=[(0, 'Playstation 4'), (1, 'Xbox One'), (2, 'PC'), (3, 'Mobile'), (4, 'Nintendo Switch'), - (5, 'Playstation 3'), (6, 'Xbox 360'), (7, 'All Consoles'), (8, 'All Platforms')], default=0), - ), - ] diff --git a/singletournaments/migrations/0007_singleeliminationtournament_allow_register.py b/singletournaments/migrations/0007_singleeliminationtournament_allow_register.py deleted file mode 100644 index 13ae1661a..000000000 --- a/singletournaments/migrations/0007_singleeliminationtournament_allow_register.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.9 on 2018-10-22 16:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0006_auto_20181018_2054'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='allow_register', - field=models.BooleanField(default=False), - ), - ] diff --git a/singletournaments/migrations/0008_merge_20181109_2110.py b/singletournaments/migrations/0008_merge_20181109_2110.py deleted file mode 100644 index 914a241f7..000000000 --- a/singletournaments/migrations/0008_merge_20181109_2110.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 2.0.8 on 2018-11-09 21:10 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0007_singleeliminationtournament_allow_register'), - ('singletournaments', '0004_singleeliminationtournament_xp_seed'), - ] - - operations = [ - ] diff --git a/singletournaments/migrations/0008_singleeliminationtournament_twitch.py b/singletournaments/migrations/0008_singleeliminationtournament_twitch.py deleted file mode 100644 index fbc777ffe..000000000 --- a/singletournaments/migrations/0008_singleeliminationtournament_twitch.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.9 on 2018-11-17 20:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0007_singleeliminationtournament_allow_register'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='twitch', - field=models.CharField(blank=True, max_length=60), - ), - ] diff --git a/singletournaments/migrations/0009_singletournamentround_info.py b/singletournaments/migrations/0009_singletournamentround_info.py deleted file mode 100644 index d3f6726d3..000000000 --- a/singletournaments/migrations/0009_singletournamentround_info.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-11-17 20:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0008_singleeliminationtournament_twitch'), - ] - - operations = [ - migrations.AddField( - model_name='singletournamentround', - name='info', - field=models.TextField(default='No info specified'), - ), - ] diff --git a/singletournaments/migrations/0010_merge_20181118_0553.py b/singletournaments/migrations/0010_merge_20181118_0553.py deleted file mode 100644 index 977c44f55..000000000 --- a/singletournaments/migrations/0010_merge_20181118_0553.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 2.0.8 on 2018-11-18 05:53 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0009_singletournamentround_info'), - ('singletournaments', '0008_merge_20181109_2110'), - ] - - operations = [ - ] diff --git a/singletournaments/migrations/0011_auto_20190112_1515.py b/singletournaments/migrations/0011_auto_20190112_1515.py deleted file mode 100644 index 50f5e5603..000000000 --- a/singletournaments/migrations/0011_auto_20190112_1515.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.4 on 2019-01-12 20:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0010_merge_20181118_0553'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.SmallIntegerField(default=0), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.SmallIntegerField(default=0), - ), - ] diff --git a/singletournaments/migrations/0012_auto_20190112_1546.py b/singletournaments/migrations/0012_auto_20190112_1546.py deleted file mode 100644 index b0b229844..000000000 --- a/singletournaments/migrations/0012_auto_20190112_1546.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.0.4 on 2019-01-12 20:46 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('singletournaments', '0011_auto_20190112_1515'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='matches.PlatformChoice'), - ), - ] diff --git a/singletournaments/migrations/0013_auto_20190313_1400.py b/singletournaments/migrations/0013_auto_20190313_1400.py deleted file mode 100644 index a04a99245..000000000 --- a/singletournaments/migrations/0013_auto_20190313_1400.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-13 18:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0012_auto_20190112_1546'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='twitch', - field=models.CharField(default='twitch', max_length=60), - ), - ] diff --git a/singletournaments/migrations/0014_auto_20190329_1944.py b/singletournaments/migrations/0014_auto_20190329_1944.py deleted file mode 100644 index 7e6f780b4..000000000 --- a/singletournaments/migrations/0014_auto_20190329_1944.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-29 23:44 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0013_auto_20190313_1400'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='game', to='matches.GameChoice'), - ), - ] diff --git a/singletournaments/migrations/0014_singleeliminationtournament_map_pool.py b/singletournaments/migrations/0014_singleeliminationtournament_map_pool.py deleted file mode 100644 index 2ebb31f03..000000000 --- a/singletournaments/migrations/0014_singleeliminationtournament_map_pool.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-16 01:52 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0008_auto_20190315_2152'), - ('singletournaments', '0013_auto_20190313_1400'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='map_pool', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='map_pool', to='matches.MapPoolChoice'), - ), - ] diff --git a/singletournaments/migrations/0015_merge_20190602_1430.py b/singletournaments/migrations/0015_merge_20190602_1430.py deleted file mode 100644 index 4df39dd71..000000000 --- a/singletournaments/migrations/0015_merge_20190602_1430.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.1.7 on 2019-06-02 18:30 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0014_singleeliminationtournament_map_pool'), - ('singletournaments', '0014_auto_20190329_1944'), - ] - - operations = [ - ] diff --git a/singletournaments/migrations/0016_auto_20190713_1335.py b/singletournaments/migrations/0016_auto_20190713_1335.py deleted file mode 100644 index 0c2d7f2f9..000000000 --- a/singletournaments/migrations/0016_auto_20190713_1335.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0015_merge_20190602_1430'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='game', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='map_pool', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='map_pool', to='matches.MapPoolChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='matches.PlatformChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='ruleset', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tournamentruleset', to='singletournaments.SingleTournamentRuleset'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='second', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='secondplaceteam', to='teams.Team'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='third', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='thirdplaceteam', to='teams.Team'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='winner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winningteam', to='teams.Team'), - ), - migrations.AlterField( - model_name='singletournamentruleset', - name='creator', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rulesetCreator', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/singletournaments/migrations/0017_singleeliminationtournament_disable_userreports.py b/singletournaments/migrations/0017_singleeliminationtournament_disable_userreports.py deleted file mode 100644 index 5d8b5cfe4..000000000 --- a/singletournaments/migrations/0017_singleeliminationtournament_disable_userreports.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0016_auto_20190713_1335'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='disable_userreports', - field=models.BooleanField(default=True), - ), - ] diff --git a/singletournaments/migrations/0018_auto_20200115_1122.py b/singletournaments/migrations/0018_auto_20200115_1122.py deleted file mode 100644 index f8dc7b000..000000000 --- a/singletournaments/migrations/0018_auto_20200115_1122.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:22 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0018_match_disable_userreports'), - ('singletournaments', '0017_singleeliminationtournament_disable_userreports'), - ] - - operations = [ - migrations.AddField( - model_name='singleeliminationtournament', - name='sports', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sport', to='matches.SportChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='game', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='matches.PlatformChoice'), - ), - ] diff --git a/singletournaments/migrations/0019_auto_20200115_1132.py b/singletournaments/migrations/0019_auto_20200115_1132.py deleted file mode 100644 index 01708a648..000000000 --- a/singletournaments/migrations/0019_auto_20200115_1132.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0018_auto_20200115_1122'), - ] - - operations = [ - migrations.RenameField( - model_name='singleeliminationtournament', - old_name='sports', - new_name='sport', - ), - ] diff --git a/singletournaments/migrations/0020_auto_20200115_1133.py b/singletournaments/migrations/0020_auto_20200115_1133.py deleted file mode 100644 index 2122b8e3b..000000000 --- a/singletournaments/migrations/0020_auto_20200115_1133.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2020-01-15 16:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0019_auto_20200115_1132'), - ] - - operations = [ - migrations.RenameField( - model_name='singleeliminationtournament', - old_name='disable_userreports', - new_name='disable_userreport', - ), - ] diff --git a/singletournaments/migrations/0021_auto_20200420_0007.py b/singletournaments/migrations/0021_auto_20200420_0007.py deleted file mode 100644 index 7c331748b..000000000 --- a/singletournaments/migrations/0021_auto_20200420_0007.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.12 on 2020-04-20 00:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('singletournaments', '0020_auto_20200115_1133'), - ] - - operations = [ - migrations.AlterField( - model_name='singleeliminationtournament', - name='game', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='game', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='platform', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='matches.PlatformChoice'), - ), - migrations.AlterField( - model_name='singleeliminationtournament', - name='sport', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sport', to='matches.SportChoice'), - ), - ] diff --git a/store/migrations/0001_initial.py b/store/migrations/0001_initial.py index fa82eb5e0..f92e63edf 100644 --- a/store/migrations/0001_initial.py +++ b/store/migrations/0001_initial.py @@ -1,10 +1,11 @@ -# Generated by Django 2.0.2 on 2018-08-03 15:10 +# Generated by Django 2.2.15 on 2020-11-17 00:03 import django.core.validators from django.db import migrations, models class Migration(migrations.Migration): + initial = True dependencies = [ @@ -18,11 +19,7 @@ class Migration(migrations.Migration): ('active', models.BooleanField()), ('business', models.EmailField(max_length=254)), ('amount', models.FloatField()), - ('item_name', models.CharField(max_length=50, validators=[ - django.core.validators.RegexValidator(code='invalid_name', - message="Item name must not have whitespace, and must be in the format 'type_num'", - regex='^[a-zA-z]+_\\d+')])), - ('price', models.CharField(max_length=50)), + ('item_name', models.CharField(max_length=50, validators=[django.core.validators.RegexValidator(code='invalid_name', message="Item name must not have whitespace, and must be in the format 'type_num'", regex='^[a-zA-z]+_\\d+')])), ('name', models.CharField(max_length=50)), ], ), diff --git a/store/migrations/0002_remove_product_price.py b/store/migrations/0002_remove_product_price.py deleted file mode 100644 index 87348abda..000000000 --- a/store/migrations/0002_remove_product_price.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-05 23:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('store', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='product', - name='price', - ), - ] diff --git a/support/migrations/0001_initial.py b/support/migrations/0001_initial.py index 45768ecd9..753e42039 100644 --- a/support/migrations/0001_initial.py +++ b/support/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:45 +# Generated by Django 2.2.15 on 2020-11-17 00:03 from django.conf import settings from django.db import migrations, models @@ -6,6 +6,7 @@ class Migration(migrations.Migration): + initial = True dependencies = [ @@ -13,24 +14,22 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='QuestionAnswerCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='General', max_length=255)), + ], + ), migrations.CreateModel( name='Ticket', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateTimeField(auto_now_add=True)), ('last_update', models.DateTimeField(auto_now=True)), - ('category', models.SmallIntegerField( - choices=[(0, 'General'), (1, 'Prize Claim'), (2, 'Tournament Support'), (3, 'Billing'), - (4, 'Refund Request'), (5, 'PSN/XBL Issues'), (6, 'Match Support/Dispute')], default=0)), ('text', models.TextField(default='A detailed description of your issue')), - ('status', - models.SmallIntegerField(choices=[(0, 'New'), (1, 'On Hold'), (2, 'In Progress'), (3, 'Closed')], - default=0)), - ('assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='assigned_tickets', to=settings.AUTH_USER_MODEL, - verbose_name='assignee')), - ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_create', - to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('status', models.SmallIntegerField(choices=[(0, 'New'), (1, 'On Hold'), (2, 'In Progress'), (3, 'Closed')], default=0)), + ('assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL, verbose_name='assignee')), ], options={ 'verbose_name': 'ticket', @@ -38,16 +37,22 @@ class Migration(migrations.Migration): 'ordering': ['date'], }, ), + migrations.CreateModel( + name='TicketCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='no name specified', max_length=255)), + ('priority', models.PositiveSmallIntegerField(default=0)), + ], + ), migrations.CreateModel( name='TicketComment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')), ('comment', models.TextField()), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name='Author')), - ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', - to='support.Ticket', verbose_name='Ticket')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Author')), + ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='support.Ticket', verbose_name='Ticket')), ], options={ 'verbose_name': 'Ticket comment', @@ -55,4 +60,26 @@ class Migration(migrations.Migration): 'ordering': ['date'], }, ), + migrations.AddField( + model_name='ticket', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_category', to='support.TicketCategory'), + ), + migrations.AddField( + model_name='ticket', + name='creator', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_create', to=settings.AUTH_USER_MODEL, verbose_name='Creator'), + ), + migrations.CreateModel( + name='QuestionAnswer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('updated', models.DateTimeField(auto_now=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('question', models.CharField(blank=True, max_length=255, null=True)), + ('answer', models.CharField(blank=True, max_length=2555, null=True)), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='qa_category', to='support.QuestionAnswerCategory')), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='qa_creator', to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ], + ), ] diff --git a/support/migrations/0002_auto_20190323_1953.py b/support/migrations/0002_auto_20190323_1953.py deleted file mode 100644 index 83775425d..000000000 --- a/support/migrations/0002_auto_20190323_1953.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.1.5 on 2019-03-23 23:53 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('support', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='TicketCategory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='no name specified', max_length=255)), - ('priority', models.PositiveSmallIntegerField(default=0)), - ], - ), - migrations.AlterField( - model_name='ticket', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_category', to='support.TicketCategory'), - ), - ] diff --git a/support/migrations/0003_auto_20190713_1335.py b/support/migrations/0003_auto_20190713_1335.py deleted file mode 100644 index b87c35cd2..000000000 --- a/support/migrations/0003_auto_20190713_1335.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('support', '0002_auto_20190323_1953'), - ] - - operations = [ - migrations.AlterField( - model_name='ticket', - name='assignee', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_tickets', to=settings.AUTH_USER_MODEL, verbose_name='assignee'), - ), - migrations.AlterField( - model_name='ticket', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ticket_category', to='support.TicketCategory'), - ), - ] diff --git a/support/migrations/0004_questionanswer_questionanswercategory.py b/support/migrations/0004_questionanswer_questionanswercategory.py deleted file mode 100644 index 376511f6a..000000000 --- a/support/migrations/0004_questionanswer_questionanswercategory.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 2.2.12 on 2020-04-20 17:56 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('support', '0003_auto_20190713_1335'), - ] - - operations = [ - migrations.CreateModel( - name='QuestionAnswerCategory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='General', max_length=255)), - ], - ), - migrations.CreateModel( - name='QuestionAnswer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('updated', models.DateTimeField(auto_now=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('question', models.CharField(blank=True, max_length=255, null=True)), - ('answer', models.CharField(blank=True, max_length=2555, null=True)), - ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='qa_category', to='support.QuestionAnswerCategory')), - ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='qa_creator', to=settings.AUTH_USER_MODEL, verbose_name='Creator')), - ], - ), - ] diff --git a/teams/migrations/0001_initial.py b/teams/migrations/0001_initial.py index 6231f2ff2..a4f72a036 100644 --- a/teams/migrations/0001_initial.py +++ b/teams/migrations/0001_initial.py @@ -1,11 +1,13 @@ -# Generated by Django 2.0.5 on 2018-07-03 23:45 +# Generated by Django 2.2.15 on 2020-11-17 00:02 from django.conf import settings from django.db import migrations, models import django.db.models.deletion +import django_countries.fields class Migration(migrations.Migration): + initial = True dependencies = [ @@ -28,18 +30,22 @@ class Migration(migrations.Migration): ('about_us', models.CharField(blank=True, default='Forever a mystery', max_length=250)), ('total_earning', models.PositiveSmallIntegerField(default=0)), ('website', models.CharField(blank=True, default='No Website', max_length=100)), - ('twitter', models.CharField(blank=True, default='No Twitter Linked', max_length=15)), - ('twitch', models.CharField(blank=True, default='No Twitch Linked', max_length=15)), + ('twitter', models.CharField(blank=True, default='None Linked', max_length=15)), + ('twitch', models.CharField(blank=True, default='None Linked', max_length=15)), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('num_matchloss', models.SmallIntegerField(default=0)), ('num_matchwin', models.SmallIntegerField(default=0)), + ('num_wagerwin', models.SmallIntegerField(default=0)), + ('num_wagerloss', models.SmallIntegerField(default=0)), ('num_tournywin', models.SmallIntegerField(default=0)), ('numtournyloss', models.SmallIntegerField(default=0)), - ('captain', models.ManyToManyField(related_name='teamcaptain', through='teams.CaptainMembership', - to=settings.AUTH_USER_MODEL)), - ('founder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='founder', - to=settings.AUTH_USER_MODEL)), + ('totalxp', models.PositiveSmallIntegerField(default=0)), + ('rank', models.PositiveSmallIntegerField(default=100)), + ('country', django_countries.fields.CountryField(blank=True, max_length=2)), + ('image', models.ImageField(blank=True, upload_to='team_images')), + ('captain', models.ManyToManyField(related_name='teamcaptain', through='teams.CaptainMembership', to=settings.AUTH_USER_MODEL)), + ('founder', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='founder', to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'Team', @@ -52,18 +58,14 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('expire', models.DateTimeField()), - ('captain', models.CharField(choices=[('captain', 'Captain'), ('player', 'Player')], default='player', - max_length=20)), + ('captain', models.CharField(choices=[('captain', 'Captain'), ('player', 'Player')], default='player', max_length=20)), ('accepted', models.BooleanField(default=False)), ('declined', models.BooleanField(default=False)), ('active', models.BooleanField(default=True)), ('hasPerms', models.BooleanField(default=False)), - ('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frominvite', - to=settings.AUTH_USER_MODEL)), - ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitedto', - to='teams.Team')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='toinvite', - to=settings.AUTH_USER_MODEL)), + ('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frominvite', to=settings.AUTH_USER_MODEL)), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitedto', to='teams.Team')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='toinvite', to=settings.AUTH_USER_MODEL)), ], ), migrations.AddField( @@ -79,7 +81,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='captainmembership', name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='captainperson', - to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='captainperson', to=settings.AUTH_USER_MODEL), ), ] diff --git a/teams/migrations/0002_auto_20181007_0517.py b/teams/migrations/0002_auto_20181007_0517.py deleted file mode 100644 index d3573e378..000000000 --- a/teams/migrations/0002_auto_20181007_0517.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-07 05:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='team', - name='twitch', - field=models.CharField(blank=True, default='None Linked', max_length=15), - ), - migrations.AlterField( - model_name='team', - name='twitter', - field=models.CharField(blank=True, default='None Linked', max_length=15), - ), - ] diff --git a/teams/migrations/0002_team_totalxp.py b/teams/migrations/0002_team_totalxp.py deleted file mode 100644 index 77f38dafe..000000000 --- a/teams/migrations/0002_team_totalxp.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-08 20:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='totalxp', - field=models.PositiveSmallIntegerField(default=0), - ), - ] diff --git a/teams/migrations/0003_auto_20181012_2134.py b/teams/migrations/0003_auto_20181012_2134.py deleted file mode 100644 index 587d72054..000000000 --- a/teams/migrations/0003_auto_20181012_2134.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-12 21:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0002_auto_20181007_0517'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='rank', - field=models.PositiveSmallIntegerField(default=100), - ), - ] diff --git a/teams/migrations/0004_merge_20181109_2114.py b/teams/migrations/0004_merge_20181109_2114.py deleted file mode 100644 index b5dca39d8..000000000 --- a/teams/migrations/0004_merge_20181109_2114.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 2.0.8 on 2018-11-09 21:14 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0003_auto_20181012_2134'), - ('teams', '0002_team_totalxp'), - ] - - operations = [ - ] diff --git a/teams/migrations/0005_team_country.py b/teams/migrations/0005_team_country.py deleted file mode 100644 index f049ad29b..000000000 --- a/teams/migrations/0005_team_country.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-08 17:40 - -from django.db import migrations -import django_countries.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0004_merge_20181109_2114'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='country', - field=django_countries.fields.CountryField(blank=True, max_length=2), - ), - ] diff --git a/teams/migrations/0006_auto_20190620_1746.py b/teams/migrations/0006_auto_20190620_1746.py deleted file mode 100644 index cca3353e1..000000000 --- a/teams/migrations/0006_auto_20190620_1746.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.2 on 2019-06-20 21:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('teams', '0005_team_country'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='num_wagerloss', - field=models.SmallIntegerField(default=0), - ), - migrations.AddField( - model_name='team', - name='num_wagerwin', - field=models.SmallIntegerField(default=0), - ), - ] diff --git a/teams/migrations/0007_auto_20190713_1335.py b/teams/migrations/0007_auto_20190713_1335.py deleted file mode 100644 index b08ca9bff..000000000 --- a/teams/migrations/0007_auto_20190713_1335.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0006_auto_20190620_1746'), - ] - - operations = [ - migrations.AlterField( - model_name='team', - name='founder', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='founder', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/teams/migrations/0008_team_image.py b/teams/migrations/0008_team_image.py deleted file mode 100644 index 72b7eefeb..000000000 --- a/teams/migrations/0008_team_image.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.2 on 2019-08-13 16:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0007_auto_20190713_1335'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='image', - field=models.ImageField(blank=True, upload_to='team_images'), - ), - ] diff --git a/wagers/migrations/0001_initial.py b/wagers/migrations/0001_initial.py index f14ce3750..615dea2ac 100644 --- a/wagers/migrations/0001_initial.py +++ b/wagers/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.5 on 2019-04-04 01:08 +# Generated by Django 2.2.15 on 2020-11-17 00:03 from django.conf import settings from django.db import migrations, models @@ -10,9 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('teams', '0005_team_country'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('matches', '0007_auto_20190112_1514'), + ('matches', '0001_initial'), + ('teams', '0001_initial'), ] operations = [ @@ -22,14 +22,16 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('info', models.TextField(default='No additional info given')), ('time', models.DateTimeField(auto_now_add=True)), + ('confirm', models.BooleanField(default=False)), + ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='challenge_user', to=settings.AUTH_USER_MODEL)), ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='challenge_team', to='teams.Team')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='challenge_user', to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='WagerMatch', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('credits', models.PositiveIntegerField(default=5)), ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='match_obj', to='matches.Match')), ], ), @@ -40,14 +42,17 @@ class Migration(migrations.Migration): ('posted', models.DateTimeField(auto_now_add=True)), ('challenge_accepted', models.BooleanField(default=False)), ('expiration', models.DateTimeField(null=True)), + ('expired', models.BooleanField(default=False)), ('credits', models.PositiveIntegerField(default=5)), ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), ('info', models.TextField(default='No additional info given')), - ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='creator_user', to=settings.AUTH_USER_MODEL)), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='game_choices', to='matches.GameChoice')), - ('platform', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='platform_choices', to='matches.PlatformChoice')), + ('challenge', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wager_challenge', to='wagers.WagerChallenge')), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='creator_user', to=settings.AUTH_USER_MODEL)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='game_choices', to='matches.GameChoice')), + ('platform', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='platform_choices', to='matches.PlatformChoice')), ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requesting_team', to='teams.Team')), + ('wmatch', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wmatch', to='wagers.WagerMatch')), ], ), ] diff --git a/wagers/migrations/0002_wagerrequest_expired.py b/wagers/migrations/0002_wagerrequest_expired.py deleted file mode 100644 index 7e55aef9a..000000000 --- a/wagers/migrations/0002_wagerrequest_expired.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-04-04 19:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='wagerrequest', - name='expired', - field=models.BooleanField(default=False), - ), - ] diff --git a/wagers/migrations/0003_auto_20190426_1731.py b/wagers/migrations/0003_auto_20190426_1731.py deleted file mode 100644 index 0be6a0808..000000000 --- a/wagers/migrations/0003_auto_20190426_1731.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.5 on 2019-04-26 21:31 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('wagers', '0002_wagerrequest_expired'), - ] - - operations = [ - migrations.AlterField( - model_name='wagerrequest', - name='creator', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='creator_user', - to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/wagers/migrations/0004_wagerchallenge_confirmed.py b/wagers/migrations/0004_wagerchallenge_confirmed.py deleted file mode 100644 index b877d8e92..000000000 --- a/wagers/migrations/0004_wagerchallenge_confirmed.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.5 on 2019-04-27 23:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('wagers', '0003_auto_20190426_1731'), - ] - - operations = [ - migrations.AddField( - model_name='wagerchallenge', - name='confirmed', - field=models.BooleanField(default=False), - ), - ] diff --git a/wagers/migrations/0005_wagerrequest_challenge.py b/wagers/migrations/0005_wagerrequest_challenge.py deleted file mode 100644 index 50be46d85..000000000 --- a/wagers/migrations/0005_wagerrequest_challenge.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-14 18:46 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0004_wagerchallenge_confirmed'), - ] - - operations = [ - migrations.AddField( - model_name='wagerrequest', - name='challenge', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wager_challenge', to='wagers.WagerChallenge'), - ), - ] diff --git a/wagers/migrations/0006_auto_20190514_1450.py b/wagers/migrations/0006_auto_20190514_1450.py deleted file mode 100644 index 7da11a059..000000000 --- a/wagers/migrations/0006_auto_20190514_1450.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-14 18:50 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0005_wagerrequest_challenge'), - ] - - operations = [ - migrations.RenameField( - model_name='wagerchallenge', - old_name='confirmed', - new_name='confirm', - ), - ] diff --git a/wagers/migrations/0007_auto_20190523_1839.py b/wagers/migrations/0007_auto_20190523_1839.py deleted file mode 100644 index 29cc176d0..000000000 --- a/wagers/migrations/0007_auto_20190523_1839.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.5 on 2019-05-23 22:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0006_auto_20190514_1450'), - ] - - operations = [ - migrations.RenameField( - model_name='wagerchallenge', - old_name='user', - new_name='creator', - ), - ] diff --git a/wagers/migrations/0008_wagermatch_credits.py b/wagers/migrations/0008_wagermatch_credits.py deleted file mode 100644 index 17c7c36f1..000000000 --- a/wagers/migrations/0008_wagermatch_credits.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-23 23:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0007_auto_20190523_1839'), - ] - - operations = [ - migrations.AddField( - model_name='wagermatch', - name='credits', - field=models.PositiveIntegerField(default=5), - ), - ] diff --git a/wagers/migrations/0009_wagerrequest_wmatch.py b/wagers/migrations/0009_wagerrequest_wmatch.py deleted file mode 100644 index e89f4656c..000000000 --- a/wagers/migrations/0009_wagerrequest_wmatch.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.1.7 on 2019-05-24 01:14 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0008_wagermatch_credits'), - ] - - operations = [ - migrations.AddField( - model_name='wagerrequest', - name='wmatch', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wmatch', to='wagers.WagerMatch'), - ), - ] diff --git a/wagers/migrations/0010_auto_20190713_1335.py b/wagers/migrations/0010_auto_20190713_1335.py deleted file mode 100644 index b34ab8c37..000000000 --- a/wagers/migrations/0010_auto_20190713_1335.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.2 on 2019-07-13 17:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagers', '0009_wagerrequest_wmatch'), - ] - - operations = [ - migrations.AlterField( - model_name='wagerrequest', - name='game', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='game_choices', to='matches.GameChoice'), - ), - migrations.AlterField( - model_name='wagerrequest', - name='platform', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='platform_choices', to='matches.PlatformChoice'), - ), - ] From fd31401932ebdc6ab49f9915ea2dec22615f3cfa Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 17 Nov 2020 12:00:53 -0500 Subject: [PATCH 065/190] reset migrations again --- leagues/migrations/0001_initial.py | 11 ++++-- matches/migrations/0001_initial.py | 37 +++++++++++++++++--- news/migrations/0001_initial.py | 2 +- pages/migrations/0001_initial.py | 2 +- profiles/migrations/0001_initial.py | 2 +- singletournaments/migrations/0001_initial.py | 4 +-- store/migrations/0001_initial.py | 2 +- support/migrations/0001_initial.py | 2 +- teams/migrations/0001_initial.py | 34 ++++++------------ wagers/migrations/0001_initial.py | 2 +- 10 files changed, 61 insertions(+), 37 deletions(-) diff --git a/leagues/migrations/0001_initial.py b/leagues/migrations/0001_initial.py index 16925a183..7f1603ce5 100644 --- a/leagues/migrations/0001_initial.py +++ b/leagues/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:03 +# Generated by Django 2.2.15 on 2020-11-17 16:59 from django.db import migrations, models import django.db.models.deletion @@ -9,9 +9,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('singletournaments', '__first__'), ('matches', '0001_initial'), ('teams', '0001_initial'), + ('singletournaments', '0001_initial'), ] operations = [ @@ -32,6 +32,13 @@ class Migration(migrations.Migration): ('auto_matchup', models.BooleanField(default=False)), ('num_divisions', models.PositiveSmallIntegerField(default=2)), ('max_division_size', models.PositiveSmallIntegerField(default=5)), + ('require_xbl', models.BooleanField(default=False)), + ('require_psn', models.BooleanField(default=False)), + ('require_steam', models.BooleanField(default=False)), + ('require_epic', models.BooleanField(default=False)), + ('require_lol', models.BooleanField(default=False)), + ('require_battlenet', models.BooleanField(default=False)), + ('require_activision', models.BooleanField(default=False)), ], ), migrations.CreateModel( diff --git a/matches/migrations/0001_initial.py b/matches/migrations/0001_initial.py index f4845a474..48dc7a49e 100644 --- a/matches/migrations/0001_initial.py +++ b/matches/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:02 +# Generated by Django 2.2.15 on 2020-11-17 16:58 from django.conf import settings from django.db import migrations, models @@ -10,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('teams', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teams', '0001_initial'), ] operations = [ @@ -90,11 +90,11 @@ class Migration(migrations.Migration): name='StatsPlayer', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rating', models.DecimalField(decimal_places=3, max_digits=6)), + ('rating', models.DecimalField(decimal_places=3, default=0, max_digits=6)), ('kills', models.IntegerField(default=0)), ('assists', models.IntegerField(default=0)), ('deaths', models.IntegerField(default=0)), - ('killround', models.DecimalField(decimal_places=3, max_digits=6)), + ('killround', models.DecimalField(decimal_places=3, default=0, max_digits=6)), ('adr', models.IntegerField(default=0)), ('ud', models.IntegerField(default=0)), ('ef', models.IntegerField(default=0)), @@ -129,6 +129,35 @@ class Migration(migrations.Migration): ('rounds_lost', models.PositiveSmallIntegerField(default=0)), ('total_kills', models.PositiveSmallIntegerField(default=0)), ('total_deaths', models.PositiveSmallIntegerField(default=0)), + ('avg_rating', models.DecimalField(decimal_places=3, default=0, max_digits=6)), + ('avg_killround', models.DecimalField(decimal_places=3, default=0, max_digits=6)), + ('avg_adr', models.IntegerField(default=0)), + ('avg_ud', models.IntegerField(default=0)), + ('avg_ef', models.IntegerField(default=0)), + ('total_rating', models.DecimalField(decimal_places=3, default=0, max_digits=6)), + ('total_killround', models.DecimalField(decimal_places=3, default=0, max_digits=6)), + ('total_adr', models.IntegerField(default=0)), + ('total_ud', models.IntegerField(default=0)), + ('total_ef', models.IntegerField(default=0)), + ('avg_hs', models.IntegerField(default=0)), + ('avg_kast', models.IntegerField(default=0)), + ('awp_k', models.IntegerField(default=0)), + ('twok', models.IntegerField(default=0)), + ('threek', models.IntegerField(default=0)), + ('fourk', models.IntegerField(default=0)), + ('fivek', models.IntegerField(default=0)), + ('one_v_one', models.IntegerField(default=0)), + ('one_v_two', models.IntegerField(default=0)), + ('one_v_three', models.IntegerField(default=0)), + ('one_v_four', models.IntegerField(default=0)), + ('one_v_five', models.IntegerField(default=0)), + ('f_kills', models.IntegerField(default=0)), + ('f_deaths', models.IntegerField(default=0)), + ('entries', models.IntegerField(default=0)), + ('trades', models.IntegerField(default=0)), + ('rounds', models.IntegerField(default=0)), + ('avg_damage', models.IntegerField(default=0)), + ('total_damage', models.IntegerField(default=0)), ], ), migrations.CreateModel( diff --git a/news/migrations/0001_initial.py b/news/migrations/0001_initial.py index add29dff8..6980f5480 100644 --- a/news/migrations/0001_initial.py +++ b/news/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:02 +# Generated by Django 2.2.15 on 2020-11-17 16:58 from django.conf import settings from django.db import migrations, models diff --git a/pages/migrations/0001_initial.py b/pages/migrations/0001_initial.py index 5270c7623..fec58b425 100644 --- a/pages/migrations/0001_initial.py +++ b/pages/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:02 +# Generated by Django 2.2.15 on 2020-11-17 16:58 from django.db import migrations, models import django.db.models.deletion diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py index 2b791a886..8807c515a 100644 --- a/profiles/migrations/0001_initial.py +++ b/profiles/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:02 +# Generated by Django 2.2.15 on 2020-11-17 16:58 from django.conf import settings from django.db import migrations, models diff --git a/singletournaments/migrations/0001_initial.py b/singletournaments/migrations/0001_initial.py index e524a108a..ab03ffdf0 100644 --- a/singletournaments/migrations/0001_initial.py +++ b/singletournaments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:03 +# Generated by Django 2.2.15 on 2020-11-17 16:59 from django.conf import settings from django.db import migrations, models @@ -10,9 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('teams', '0001_initial'), ('matches', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ diff --git a/store/migrations/0001_initial.py b/store/migrations/0001_initial.py index f92e63edf..3d61e1e2c 100644 --- a/store/migrations/0001_initial.py +++ b/store/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:03 +# Generated by Django 2.2.15 on 2020-11-17 16:59 import django.core.validators from django.db import migrations, models diff --git a/support/migrations/0001_initial.py b/support/migrations/0001_initial.py index 753e42039..8f4bbda90 100644 --- a/support/migrations/0001_initial.py +++ b/support/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:03 +# Generated by Django 2.2.15 on 2020-11-17 16:59 from django.conf import settings from django.db import migrations, models diff --git a/teams/migrations/0001_initial.py b/teams/migrations/0001_initial.py index a4f72a036..f8cd0bd90 100644 --- a/teams/migrations/0001_initial.py +++ b/teams/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:02 +# Generated by Django 2.2.15 on 2020-11-17 16:58 from django.conf import settings from django.db import migrations, models @@ -15,13 +15,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='CaptainMembership', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('active', models.BooleanField(default=True)), - ], - ), migrations.CreateModel( name='Team', fields=[ @@ -44,8 +37,9 @@ class Migration(migrations.Migration): ('rank', models.PositiveSmallIntegerField(default=100)), ('country', django_countries.fields.CountryField(blank=True, max_length=2)), ('image', models.ImageField(blank=True, upload_to='team_images')), - ('captain', models.ManyToManyField(related_name='teamcaptain', through='teams.CaptainMembership', to=settings.AUTH_USER_MODEL)), + ('captain', models.ManyToManyField(related_name='teamcaptain', to=settings.AUTH_USER_MODEL)), ('founder', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='founder', to=settings.AUTH_USER_MODEL)), + ('players', models.ManyToManyField(related_name='teamplayers', to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'Team', @@ -68,19 +62,13 @@ class Migration(migrations.Migration): ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='toinvite', to=settings.AUTH_USER_MODEL)), ], ), - migrations.AddField( - model_name='team', - name='players', - field=models.ManyToManyField(through='teams.TeamInvite', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='captainmembership', - name='team', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team', to='teams.Team'), - ), - migrations.AddField( - model_name='captainmembership', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='captainperson', to=settings.AUTH_USER_MODEL), + migrations.CreateModel( + name='CaptainMembership', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active', models.BooleanField(default=True)), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team', to='teams.Team')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='captainperson', to=settings.AUTH_USER_MODEL)), + ], ), ] diff --git a/wagers/migrations/0001_initial.py b/wagers/migrations/0001_initial.py index 615dea2ac..501141dd4 100644 --- a/wagers/migrations/0001_initial.py +++ b/wagers/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 00:03 +# Generated by Django 2.2.15 on 2020-11-17 16:59 from django.conf import settings from django.db import migrations, models From 65ccee1ccaa290846f3e066f6e2f030d45a58108 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 17 Nov 2020 12:01:09 -0500 Subject: [PATCH 066/190] update from stash --- leagues/models.py | 9 ++++++++ leagues/views.py | 16 +++++++++++-- matches/models.py | 35 +++++++++++++++++++++++++++-- matches/urls.py | 5 +++-- matches/views.py | 57 ++++++++++++++++++++++++++++++++++++++++++++--- teams/models.py | 4 ++-- 6 files changed, 115 insertions(+), 11 deletions(-) diff --git a/leagues/models.py b/leagues/models.py index ae44f42f2..b4ed33f7d 100644 --- a/leagues/models.py +++ b/leagues/models.py @@ -40,6 +40,15 @@ class LeagueSettings(models.Model): num_divisions = models.PositiveSmallIntegerField(default=2) # max amount of teams to allow into a division max_division_size = models.PositiveSmallIntegerField(default=5) + # is xbl required to join? + require_xbl = models.BooleanField(default=False) + require_psn = models.BooleanField(default=False) + require_steam = models.BooleanField(default=False) + require_epic = models.BooleanField(default=False) + require_lol = models.BooleanField(default=False) + require_battlenet = models.BooleanField(default=False) + require_activision = models.BooleanField(default=False) + def __str__(self): return self.name diff --git a/leagues/views.py b/leagues/views.py index 7dafa08a1..0fc73ef5c 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -2,7 +2,7 @@ from django.contrib import messages from django.shortcuts import redirect from .models import League, LeagueDivision, LeagueSettings - +from profiles.models import User, UserProfile def list_leagues(request): leagues = League.objects.filter(active=True) @@ -24,13 +24,25 @@ def detail_league(request, pk): def join_league(request, pk): league = get_object_or_404(League, pk=pk) # TODO - create join league form and send to template + userprofile = UserProfile.objects.get(user__username=request.user.username) + # TODO - create join league form and send to template if request.method == 'GET': # send the form pass elif request.method == 'POST': # try and get them to join the league # make sure enough players exist on the team - pass + if league.req_credits > 0: + # there is a credit fee, check the user has enough credits + if userprofile.credits >= league.req_credits: + # they do have enough credits + userprofile.credits = userprofile.credits - league.req_credits + userprofile.save() + else: + messages.error(request, "You do not have enough credits to enter your team in this league") + return redirect('leagues:detail', pk=pk) + # now lets check that the team has enough players + return render(request, 'leagues/league_join.html', {'league': league}) diff --git a/matches/models.py b/matches/models.py index e8eb9cbbb..1932580d7 100644 --- a/matches/models.py +++ b/matches/models.py @@ -6,11 +6,11 @@ class StatsPlayer(models.Model): - rating = models.DecimalField(max_digits=6, decimal_places=3) + rating = models.DecimalField(max_digits=6, decimal_places=3, default=0) kills = models.IntegerField(default=0) assists = models.IntegerField(default=0) deaths = models.IntegerField(default=0) - killround = models.DecimalField(max_digits=6, decimal_places=3) + killround = models.DecimalField(max_digits=6, decimal_places=3, default=0) adr = models.IntegerField(default=0) ud = models.IntegerField(default=0) ef = models.IntegerField(default=0) @@ -42,6 +42,37 @@ class TeamMatchStats(models.Model): rounds_lost = models.PositiveSmallIntegerField(default=0) total_kills = models.PositiveSmallIntegerField(default=0) total_deaths = models.PositiveSmallIntegerField(default=0) + avg_rating = models.DecimalField(max_digits=6, decimal_places=3, default=0) + avg_killround = models.DecimalField(max_digits=6, decimal_places=3, default=0) + avg_adr = models.IntegerField(default=0) + avg_ud = models.IntegerField(default=0) + avg_ef = models.IntegerField(default=0) + total_rating = models.DecimalField(max_digits=6, decimal_places=3, default=0) + total_killround = models.DecimalField(max_digits=6, decimal_places=3, default=0) + total_adr = models.IntegerField(default=0) + total_ud = models.IntegerField(default=0) + total_ef = models.IntegerField(default=0) + # ??? f_assists = models.IntegerField(default=0) + avg_hs = models.IntegerField(default=0) + avg_kast = models.IntegerField(default=0) + awp_k = models.IntegerField(default=0) + twok = models.IntegerField(default=0) + threek = models.IntegerField(default=0) + fourk = models.IntegerField(default=0) + fivek = models.IntegerField(default=0) + one_v_one = models.IntegerField(default=0) + one_v_two = models.IntegerField(default=0) + one_v_three = models.IntegerField(default=0) + one_v_four = models.IntegerField(default=0) + one_v_five = models.IntegerField(default=0) + f_kills = models.IntegerField(default=0) + f_deaths = models.IntegerField(default=0) + entries = models.IntegerField(default=0) + trades = models.IntegerField(default=0) + # rounds played + rounds = models.IntegerField(default=0) + avg_damage = models.IntegerField(default=0) + total_damage = models.IntegerField(default=0) class MatchStats(models.Model): diff --git a/matches/urls.py b/matches/urls.py index dd3b963d4..7beac78ec 100644 --- a/matches/urls.py +++ b/matches/urls.py @@ -10,6 +10,7 @@ path('/', login_required(TournamentMatchDetailView.as_view()), name='detail'), path('/report/', login_required(MatchReportCreateView.as_view()), name='report'), path('/dispute/', login_required(MatchDisputeReportCreateView.as_view()), name='dispute'), - path('maps//', login_required(MapPoolDetail.as_view()), name='maps_detail') - + path('maps//', login_required(MapPoolDetail.as_view()), name='maps_detail'), + path('/checkin/', login_required(match_checkin), name='checkin'), + path('/checkin//', login_required(team_checkin), name='team_checkin') ] diff --git a/matches/views.py b/matches/views.py index 27c23d424..4dd16aba2 100644 --- a/matches/views.py +++ b/matches/views.py @@ -6,10 +6,10 @@ from django.shortcuts import render, redirect from django.template.loader import render_to_string from django.views.generic import DetailView, CreateView, View - -from matches.models import Match, MatchReport, MatchDispute, MapPoolChoice +from profiles.models import UserProfile +from matches.models import Match, MatchReport, MatchDispute, MapPoolChoice, MatchCheckIn from teams.models import Team, TeamInvite -from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm +from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm, TeamCheckInForm class MapPoolDetail(DetailView): @@ -230,3 +230,54 @@ def form_valid(self, form, **kwargs): dispute.team1origreporter = matchreport_1.reporting_user dispute.save() return redirect('matches:detail', pk=self.kwargs['pk']) + + +def match_checkin(request, pk): + match = Match.objects.get(pk=pk) + user = UserProfile.objects.get(user__username=request.user.username) + away = match.awayteam + home = match.hometeam + if user not in away.captain or user not in home.captain or user is not away.founder or user is not home.founder: + messages.error(request, "You don't have permission to checkin for this match") + return redirect('match:detail', pk=pk) + else: + return render(request, 'matches/match_checkin.html', {'match': match}) + + +def team_checkin(request, pk, teamid): + # get the team they're trying to checkin from the get + team = Team.objects.get(pk=teamid) + # get the match from the pk in the url + match = Match.objects.get(pk=pk) + # get logged in user + user = UserProfile.objects.get(user__username=request.user.username) + if user is not team.founder or user not in team.captain: + # they don't have perms - gtfo + messages.error(request, "You do not have permissions to checkin this team") + return redirect('matches:detail', pk=pk) + if request.method == 'GET': + # send the form, render + form = TeamCheckInForm(request.GET) + return render(request, 'matches/team_checkin.html', {'form': form}) + pass + elif request.method == 'POST': + # lets make it and get the data + # TODO: find a way to get the team instance in the form + form = TeamCheckInForm(request.POST,) + if form.is_valid(): + temp = MatchCheckIn() + temp.match = match + temp.team = team + temp.reporter = user + # TODO: verify posted data from form of field 'players' + temp.players = form.cleaned_data['players'] + temp.save() + messages.success(request, 'Your team has been checked in, checkin #'+temp.pk) + return redirect('matches:detail', pk=match.pk) + else: + messages.error(request, "Form error, this should not occur") + return redirect('matches:detail', pk=pk) + + +def create_checkin(request, pk): + pass \ No newline at end of file diff --git a/teams/models.py b/teams/models.py index a0c65dfaf..9daca10eb 100644 --- a/teams/models.py +++ b/teams/models.py @@ -21,9 +21,9 @@ class Team(models.Model): # whoever filled out the form to create the team, limited to only one founder = models.ForeignKey(User, related_name='founder', on_delete=models.SET_NULL, null=True) # basically founder permissions, but to other people that didn't create the actual team - captain = models.ManyToManyField(User, through='CaptainMembership', related_name='teamcaptain') + captain = models.ManyToManyField(User, related_name='teamcaptain') # the people of the actual team, now a many to many, not a forkey - players = models.ManyToManyField(User, through='TeamInvite', through_fields=('team', 'user', 'inviter')) + players = models.ManyToManyField(User, related_name='teamplayers') # when they created the team created = models.DateTimeField(auto_now_add=True) # when they last updated anything in the team From f304b4936a7720afdbadec40674ff5451ce487f6 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 19 Nov 2020 21:41:08 -0500 Subject: [PATCH 067/190] temp remove staff tests --- staff/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/staff/tests.py b/staff/tests.py index ffd416976..65a8e1aec 100644 --- a/staff/tests.py +++ b/staff/tests.py @@ -2,7 +2,7 @@ from .views import * - +""" class StaffBasicTest1(TestCase): fixtures = ['pages.json'] @@ -198,3 +198,4 @@ def test_partner_list(self): response = users(request) self.assertEqual(response.status_code, 200) print('Completed staff:partner_list') +""" From 8eeebbadc04d642a8f92011b005ba2ed0a8a4d3f Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 21 Nov 2020 22:04:46 -0500 Subject: [PATCH 068/190] pep8 --- matches/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/matches/models.py b/matches/models.py index 1932580d7..ac749e48e 100644 --- a/matches/models.py +++ b/matches/models.py @@ -1,6 +1,5 @@ from django.contrib.auth.models import User from django.db import models - from matches.settings import TEAMFORMAT_CHOICES, MAPFORMAT_CHOICES from teams.models import Team From d7a288f75b899a7bb3887c277518fadd04eff91e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 21 Nov 2020 22:05:14 -0500 Subject: [PATCH 069/190] teams add matches and team_stat - closes #109 --- teams/migrations/0002_auto_20201121_2203.py | 24 +++++++++++++++++++++ teams/models.py | 4 +++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 teams/migrations/0002_auto_20201121_2203.py diff --git a/teams/migrations/0002_auto_20201121_2203.py b/teams/migrations/0002_auto_20201121_2203.py new file mode 100644 index 000000000..d2d7a936e --- /dev/null +++ b/teams/migrations/0002_auto_20201121_2203.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.15 on 2020-11-22 03:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0001_initial'), + ('teams', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='matches', + field=models.ManyToManyField(related_name='team_matches', to='matches.Match'), + ), + migrations.AddField( + model_name='team', + name='team_stat', + field=models.ManyToManyField(related_name='match_team_stat', to='matches.TeamMatchStats'), + ), + ] diff --git a/teams/models.py b/teams/models.py index 9daca10eb..ba3d7b903 100644 --- a/teams/models.py +++ b/teams/models.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from django.db import models from django_countries.fields import CountryField - +#from matches.models import Match, TeamMatchStats from profiles.models import UserProfile @@ -44,6 +44,8 @@ class Team(models.Model): country = CountryField(blank=True) image = models.ImageField(upload_to='team_images', blank=True) + matches = models.ManyToManyField('matches.Match', related_name='team_matches') + team_stat = models.ManyToManyField('matches.TeamMatchStats', related_name='match_team_stat') class Meta: verbose_name = 'Team' From 54bf8809910ed5446520728bf2ff4f3d7a4d8286 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Sat, 21 Nov 2020 22:07:21 -0500 Subject: [PATCH 070/190] remove tests in news - temp --- news/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/tests.py b/news/tests.py index 787871c44..a55599cb3 100644 --- a/news/tests.py +++ b/news/tests.py @@ -4,7 +4,7 @@ from .views import * -class PostTestCase1(TestCase): +"""class PostTestCase1(TestCase): fixtures = ['pages.json'] def setUp(self): @@ -50,3 +50,4 @@ def test_list2response(self): response = post_list(request) self.assertEqual(response.status_code, 200) +""" \ No newline at end of file From c72f28f283a47e265fd63393fc9dbd9e753bad80 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:06:42 -0500 Subject: [PATCH 071/190] add mtm in userprofiles to track team membership --- .../migrations/0002_auto_20201201_2120.py | 29 +++++++++++++++++++ profiles/models.py | 6 ++++ 2 files changed, 35 insertions(+) create mode 100644 profiles/migrations/0002_auto_20201201_2120.py diff --git a/profiles/migrations/0002_auto_20201201_2120.py b/profiles/migrations/0002_auto_20201201_2120.py new file mode 100644 index 000000000..74288966d --- /dev/null +++ b/profiles/migrations/0002_auto_20201201_2120.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.12 on 2020-12-02 02:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0002_auto_20201121_2203'), + ('profiles', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='captain_teams', + field=models.ManyToManyField(related_name='profile_captain_teams', to='teams.Team'), + ), + migrations.AddField( + model_name='userprofile', + name='founder_teams', + field=models.ManyToManyField(related_name='profile_founder_teams', to='teams.Team'), + ), + migrations.AddField( + model_name='userprofile', + name='player_teams', + field=models.ManyToManyField(related_name='profile_player_teams', to='teams.Team'), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index c4bccc51a..90f2f8794 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -63,6 +63,12 @@ def __str__(self): country = CountryField(blank_label='(select country)', default='US') email_enabled = models.BooleanField(default=True) + # teams the user founded + founder_teams = models.ManyToManyField('teams.Team', related_name='profile_founder_teams') + # teams the user is a captain of + captain_teams = models.ManyToManyField('teams.Team', related_name='profile_captain_teams') + # teams the user is a player on + player_teams = models.ManyToManyField('teams.Team', related_name='profile_player_teams') def calculate_rank(self): self.rank = int(UserProfile.objects.filter(xp__gt=self.xp).count()) + 1 From 716a6198cb79feac26c5fc7ad6ca7ab9a2fcab55 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:07:14 -0500 Subject: [PATCH 072/190] add bare try except for sending emails in testing env's - fix urls --- teams/views.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/teams/views.py b/teams/views.py index 26e33c616..42b11cf1f 100644 --- a/teams/views.py +++ b/teams/views.py @@ -255,13 +255,17 @@ def post(self, request): email = EmailMessage( mail_subject, message, from_email=settings.FROM_EMAIL, to=[invitee.user.email] ) - email.send() + try: + email.send() + except: + messages.error(request, 'Unable to send email') + return redirect('teams:list') messages.success(request, 'Sent invite successfully') - return redirect('/teams/') + return redirect('teams:list') else: messages.error(request, "You must be a captain or the founder to invite") - return redirect('/teams/') + return redirect('teams:list') class LeaveTeamView(View): From ac67ad3d1ec1912a13128090592764f3777e5882 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:07:30 -0500 Subject: [PATCH 073/190] pep8 --- teams/views.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/teams/views.py b/teams/views.py index 42b11cf1f..1d07dc372 100644 --- a/teams/views.py +++ b/teams/views.py @@ -98,13 +98,13 @@ def edit_team_view(request, pk): teamobj = get_object_or_404(Team, id=pk) form = EditTeamProfileForm(request.POST, request.FILES, instance=teamobj) if form.is_valid(): - #teamobj.about_us = form.data['about_us'] - #teamobj.website = form.data['website'] - #teamobj.twitter = form.data['twitter'] - #teamobj.twitch = form.data['twitch'] - #teamobj.country = form.data['country'] - #teamobj.image = form.data['image'] - #teamobj.save() + # teamobj.about_us = form.data['about_us'] + # teamobj.website = form.data['website'] + # teamobj.twitter = form.data['twitter'] + # teamobj.twitch = form.data['twitch'] + # teamobj.country = form.data['country'] + # teamobj.image = form.data['image'] + # teamobj.save() form.save() messages.success(request, 'Team successfully updated') return redirect(reverse('teams:detail', args=[pk])) @@ -139,9 +139,11 @@ def get(self, request, pk): messages.warning(request, "Xbox Live is not verified") if not user.psn_verified: messages.warning(request, "PSN is not verified") - return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'up':up,'pk': pk, 'matches': matches}) + return render(request, 'teams/team_detail.html', + {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) else: - return render(request, 'teams/team_detail.html', {'team': team, 'players': players, 'up':up, 'pk': pk, 'matches': matches}) + return render(request, 'teams/team_detail.html', + {'team': team, 'players': players, 'up': up, 'pk': pk, 'matches': matches}) def get_context_date(self, **kwargs): context = super(MyTeamDetailView, self).get_context_date(**kwargs) @@ -295,7 +297,8 @@ def post(self, request, pk): messages.error(request, "You don't appear to be on this team") return redirect('teams:detail', pk=pk) except: - messages.error(request, "You submitted without confirming that you wanted to leave, redirecting to team detail") + messages.error(request, + "You submitted without confirming that you wanted to leave, redirecting to team detail") return redirect('teams:detail', pk=pk) From f6c2c9369eafaf5fd0680930d59bb29fdbe9ae43 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:07:59 -0500 Subject: [PATCH 074/190] team views modification to fit with profile teams mtm fields --- teams/views.py | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/teams/views.py b/teams/views.py index 1d07dc372..880184f66 100644 --- a/teams/views.py +++ b/teams/views.py @@ -65,19 +65,32 @@ def invite_view(request, num): invite.expire = timezone.now() invite.active = False invite.save() - messages.success(request, 'Accepted invite to '+str(invite.team.name)) - return redirect('/teams/') + if invite.hasPerms: + profile = UserProfile.objects.get(user=invite.user) + profile.captain_teams.add(invite.team) + profile.save() + messages.success(request, 'Successfully added the team to your profile as a captain') + else: + profile = UserProfile.objects.get(user=invite.user) + profile.player_teams.add(invite.team) + profile.save() + messages.success(request, 'Successfully added the team to your profile as a player') + # TODO: verify + invite.team.players.add(invite.user) + invite.team.save() + messages.success(request, 'Accepted invite to ' + str(invite.team.name)) + return redirect('teams:list') elif accepted == 'off': invite = TeamInvite.objects.get(id=num) invite.declined = True invite.expire = timezone.now() invite.active = False invite.save() - messages.success(request, 'Declined invite to '+str(invite.team.name)) - return redirect('/teams/') + messages.success(request, 'Declined invite to ' + str(invite.team.name)) + return redirect('teams:list') -class MyTeamsListView(ListView): +"""class MyTeamsListView(ListView): # list all the teams they are apart of # maybe list the role they have? model = Team @@ -91,6 +104,17 @@ def get_queryset(self, **kwargs): if TeamInvite.objects.filter(user=self.request.user, accepted=True): # TO DO switch the filter to the players field not just the founder field. return TeamInvite.objects.filter(user=self.request.user, accepted=True) +""" + + +def MyTeamsListView(request): + profile = UserProfile.objects.get(user=request.user) + # team_list = Team.objects.filter(players__in=[request.user]) + # team_list = Team.objects.filter(Q(founder=request.user) or Q(captain=request.user) or Q(players__in=[request.user])) + # team_list = TeamInvite.objects.filter(user=request.user, accepted=True, active=True) + return render(request, 'teams/team_list.html', + {'founder_teams': profile.founder_teams.all(), 'captain_teams': profile.captain_teams.all(), + 'player_teams': profile.player_teams.all()}) def edit_team_view(request, pk): @@ -189,7 +213,10 @@ def post(self, request): return redirect('teams:create') Team.founder = self.request.user + profile = UserProfile.objects.get(user=request.user) Team.save() + profile.founder_teams.add(Team) + profile.save() invite = TeamInvite() invite.expire = timezone.now() invite.user = self.request.user @@ -290,6 +317,11 @@ def post(self, request, pk): invites = TeamInvite.objects.filter(team_id=pk) if not invites.exists(): team = Team.objects.get(id=pk) + profile = UserProfile.objects.get(user=request.user) + try: + profile.founder_teams.remove(team) + except: + messages.error(request, 'Unable to remove the team from your profile') team.delete() messages.success(request, 'Deleted team due to the last user leaving') return redirect('teams:list') From c8fb8a02a2dc70ae6ae870487bef73007c57ffe5 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:08:40 -0500 Subject: [PATCH 075/190] more accurately show the invite form to the user --- teams/forms.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/teams/forms.py b/teams/forms.py index e01f033cd..bb3bbbf6c 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -1,10 +1,10 @@ from django import forms - +from django.db.models import Q # import the actual team model for the create team forms from teams.models import Team # import the model for the team invite from teams.models import TeamInvite - +from profiles.models import UserProfile # forms to create a team of various sizes @@ -43,7 +43,10 @@ def __init__(self, request, *args, **kwargs): self.username = request.user invites = TeamInvite.objects.filter(hasPerms=True, user=request.user, accepted=True) - teams = Team.objects.filter(id__in=invites.values_list('team')) + profile = UserProfile.objects.get(user=request.user) + tlist = profile.captain_teams.all() | profile.founder_teams.all() + # tlist = profile.captain_teams.all() + profile.founder_teams.all() + teams = tlist #profile.captain_teams.all() + profile.founder_teams.all()) # super().__init__(*args, **kwargs) self.fields['team'].queryset = teams From 22d08bccba19ec2e655acf508ea3abae381b48b0 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:09:08 -0500 Subject: [PATCH 076/190] use object q for queries in team views --- teams/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/teams/views.py b/teams/views.py index 880184f66..a287ad146 100644 --- a/teams/views.py +++ b/teams/views.py @@ -10,6 +10,7 @@ from django.template.loader import render_to_string from django.urls import reverse from django.utils import timezone +from django.db.models import Q from django.views.generic import ListView, DetailView, View from matches.models import Match From 07dd0668ea851a1add7b8ada8b262ae0a0b7ab65 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:09:22 -0500 Subject: [PATCH 077/190] shift team list view away from class based view --- teams/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teams/urls.py b/teams/urls.py index 7d7a3c7e5..56645ce3a 100644 --- a/teams/urls.py +++ b/teams/urls.py @@ -8,7 +8,7 @@ app_name = 'teams' urlpatterns = [ - path('', login_required(MyTeamsListView.as_view()), name='list'), + path('', login_required(MyTeamsListView), name='list'), path('invites/', login_required(MyInvitesListView.as_view()), name='myinvitelist'), path('invites//', login_required(invite_view), name='invite_detail'), path('/', MyTeamDetailView.as_view(), name='detail'), From 24bafb73ffb6607a5b7615894c41134ac1c584d6 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:09:41 -0500 Subject: [PATCH 078/190] remove captain membership from teams models - use profile mtm fields now --- .../migrations/0003_delete_captainmembership.py | 16 ++++++++++++++++ teams/models.py | 6 ------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 teams/migrations/0003_delete_captainmembership.py diff --git a/teams/migrations/0003_delete_captainmembership.py b/teams/migrations/0003_delete_captainmembership.py new file mode 100644 index 000000000..f5b980ece --- /dev/null +++ b/teams/migrations/0003_delete_captainmembership.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.12 on 2020-12-02 02:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0002_auto_20201121_2203'), + ] + + operations = [ + migrations.DeleteModel( + name='CaptainMembership', + ), + ] diff --git a/teams/models.py b/teams/models.py index ba3d7b903..02a2d2158 100644 --- a/teams/models.py +++ b/teams/models.py @@ -90,9 +90,3 @@ class TeamInvite(models.Model): def __str__(self): return str(self.user) - - -class CaptainMembership(models.Model): - user = models.ForeignKey(User, related_name='captainperson', on_delete=models.CASCADE) - team = models.ForeignKey(Team, related_name='team', on_delete=models.CASCADE) - active = models.BooleanField(default=True) From e672a2d9b41e3c0327ccbd13423dac94d3287583 Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:10:08 -0500 Subject: [PATCH 079/190] rework team_list template to be more detailed and work off new views --- project-templates/teams/team_list.html | 50 +++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/project-templates/teams/team_list.html b/project-templates/teams/team_list.html index f5b9f3474..621e73daa 100644 --- a/project-templates/teams/team_list.html +++ b/project-templates/teams/team_list.html @@ -6,6 +6,10 @@ {% endblock %} {% block body %} +

View team invites

+
+
+

Teams Founded

@@ -14,12 +18,50 @@ - {% for TeamInvite in team_list %} + {% for team in founder_teams %} - - - + + + + + + {% endfor %} +
Date Created
{{ TeamInvite.team.id }}{{ TeamInvite.team.name }}{{ TeamInvite.team.created }}{{ team.id }}{{ team.name }}{{ team.created }}
+

Active Captain Teams

+ + + + + + + + + {% for team in captain_teams %} + + + + + + + + {% endfor %} +
IDTeam NameDate Created
{{ team.id }}{{ team.name }}{{ team.created }}
+

Active Player Teams

+ + + + + + + + + {% for team in player_teams %} + + + + + {% endfor %} From b89ed70fa88b3dbadc1150f694ce0123b848e3af Mon Sep 17 00:00:00 2001 From: Mike M Date: Tue, 1 Dec 2020 22:12:10 -0500 Subject: [PATCH 080/190] add allow fa field to leagues - fix pr conflicts --- .../migrations/0002_leaguesettings_allow_fa.py | 18 ++++++++++++++++++ leagues/models.py | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 leagues/migrations/0002_leaguesettings_allow_fa.py diff --git a/leagues/migrations/0002_leaguesettings_allow_fa.py b/leagues/migrations/0002_leaguesettings_allow_fa.py new file mode 100644 index 000000000..50d97b7a4 --- /dev/null +++ b/leagues/migrations/0002_leaguesettings_allow_fa.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-12-02 03:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('leagues', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='leaguesettings', + name='allow_fa', + field=models.BooleanField(default=False), + ), + ] diff --git a/leagues/models.py b/leagues/models.py index b4ed33f7d..c9f9816d3 100644 --- a/leagues/models.py +++ b/leagues/models.py @@ -48,6 +48,8 @@ class LeagueSettings(models.Model): require_lol = models.BooleanField(default=False) require_battlenet = models.BooleanField(default=False) require_activision = models.BooleanField(default=False) + # whether or not to allow users to register as a free agent to the league + allow_fa = models.BooleanField(default=False) def __str__(self): From e10b22b2b899a6c5bd64a36624e7ca5b554db670 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 3 Dec 2020 21:14:54 -0500 Subject: [PATCH 081/190] Create Jenkinsfile --- Jenkinsfile | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000..940e77af3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,43 @@ +pipeline { + agent any + + stages { + stage('Build') { + steps { + sh '''#!/bin/bash + docker build -t nfmstudios/project-olly:master . + docker network create -d bridge project-olly-net + docker run -d --network=project-olly-net -e "POSTGRES_PASSWORD=secret_password" -e "POSTGRES_USER=olly" --name project-olly-db postgres:12 + sleep 30 + ''' + } + } + stage('Test') { + steps { + sh '''#!/bin/bash + docker run --network=project-olly-net --env-file .env.example --name project-olly nfmstudios/project-olly:master python3 manage.py test + ''' + } + } + } + post { + success { + withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: '$webhook_url' + } + } + failure { + withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: '$webhook_url' + } + } + cleanup { + sh '''#!/bin/bash + docker rm -f project-olly 2> /dev/null + docker rm -f project-olly-db 2> /dev/null + docker network rm project-olly-net 2> /dev/null + docker rmi nfmstudios/project-olly:master 2> /dev/null + ''' + } + } +} From 1b690de74f94efb1f9d76ee2b17987f0b3ad2fb0 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 3 Dec 2020 21:31:28 -0500 Subject: [PATCH 082/190] Update Jenkinsfile double quotes --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 940e77af3..5b4534573 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,12 +23,12 @@ pipeline { post { success { withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: '$webhook_url' + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: "$webhook_url" } } failure { withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: '$webhook_url' + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: "$webhook_url" } } cleanup { From 2d6076d72c276042c72bcdbbfa67aeb27a2ff4ed Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 3 Dec 2020 21:46:56 -0500 Subject: [PATCH 083/190] Webhooks are fun --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5b4534573..11848b6e9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,13 +22,13 @@ pipeline { } post { success { - withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: "$webhook_url" + withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: 'env.WEBHOOK_URL' } } failure { withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: "$webhook_url" + discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: 'env.WEBHOOK_URL' } } cleanup { From 1d5269d6da7e130da08b2c9dd8fce622013d7008 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 3 Dec 2020 21:56:30 -0500 Subject: [PATCH 084/190] Try the right(?) syntax --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 11848b6e9..f28937cbe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,12 +23,12 @@ pipeline { post { success { withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'SUCCESS', title: 'env.JOB_NAME', webhookURL: 'env.WEBHOOK_URL' + discordSend description: "Build Success", link: env.BUILD_URL, result: currentBuild.currentResult, title: JOB_NAME, webhookURL: env.WEBHOOK_URL } } failure { - withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: 'Build Success', link: 'env.BUILD_URL', result: 'FAILURE', title: 'env.JOB_NAME', webhookURL: 'env.WEBHOOK_URL' + withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { + discordSend description: "Build Failure", link: env.BUILD_URL, result: currentBuild.currentResult, title: JOB_NAME, webhookURL: env.WEBHOOK_URL } } cleanup { From 5041bf335dbe99d3b326c79bef3fef19dd6cb8bf Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sat, 5 Dec 2020 17:32:32 -0500 Subject: [PATCH 085/190] Add more info to jenkins discord notification --- Jenkinsfile | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f28937cbe..47db95dfc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -21,14 +21,21 @@ pipeline { } } post { - success { - withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "Build Success", link: env.BUILD_URL, result: currentBuild.currentResult, title: JOB_NAME, webhookURL: env.WEBHOOK_URL - } + always { + def msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" + msg += "**Branch:** ${branch}\n" + msg += "**Changes:** \n" + if (!currentBuild.changeSets.isEmpty()) { + currentBuild.changeSets.first().getLogs().each { + msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + "*\n" } - failure { - withCredentials([string(credentialsId: 'Webook_URL', variable: 'webhook_url')]) { - discordSend description: "Build Failure", link: env.BUILD_URL, result: currentBuild.currentResult, title: JOB_NAME, webhookURL: env.WEBHOOK_URL + } else { + msg += "no changes for this run\n" + } + + if (msg.length() > 1024) msg.take(msg.length() - 1024) + withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { + discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${branch} #${BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL } } cleanup { From 30fe15d8509281d98aa7bfd7c18b761be7c464a2 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sat, 5 Dec 2020 17:52:49 -0500 Subject: [PATCH 086/190] Try putting this in a script step --- Jenkinsfile | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 47db95dfc..44c8983fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,18 +22,19 @@ pipeline { } post { always { - def msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" - msg += "**Branch:** ${branch}\n" - msg += "**Changes:** \n" - if (!currentBuild.changeSets.isEmpty()) { - currentBuild.changeSets.first().getLogs().each { + script { + def msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" + msg += "**Branch:** ${branch}\n" + msg += "**Changes:** \n" + if (!currentBuild.changeSets.isEmpty()) { + currentBuild.changeSets.first().getLogs().each { msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + "*\n" + } + } else { + msg += "no changes for this run\n" + } + if (msg.length() > 1024) msg.take(msg.length() - 1024) } - } else { - msg += "no changes for this run\n" - } - - if (msg.length() > 1024) msg.take(msg.length() - 1024) withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${branch} #${BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL } From 607774050f5aa8e26f32b011e94910b245908a2c Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sun, 6 Dec 2020 13:02:22 -0500 Subject: [PATCH 087/190] Different env var format --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 44c8983fb..d4c655ca9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,7 +36,7 @@ pipeline { if (msg.length() > 1024) msg.take(msg.length() - 1024) } withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${branch} #${BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL + discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL } } cleanup { From fd543c9ddddcb384c1e4fb3f2279496d548fd916 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sun, 6 Dec 2020 13:06:02 -0500 Subject: [PATCH 088/190] Try GIT_LOCAL_BRANCH --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d4c655ca9..82a019e17 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,7 +24,7 @@ pipeline { always { script { def msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" - msg += "**Branch:** ${branch}\n" + msg += "**Branch:** ${env.GIT_LOCAL_BRANCH}\n" msg += "**Changes:** \n" if (!currentBuild.changeSets.isEmpty()) { currentBuild.changeSets.first().getLogs().each { @@ -36,7 +36,7 @@ pipeline { if (msg.length() > 1024) msg.take(msg.length() - 1024) } withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL + discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.GIT_LOCAL_BRANCH} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL } } cleanup { From ef38be5394f7fd0b4ea079b71cb9a84ab3e540a2 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sun, 6 Dec 2020 13:38:25 -0500 Subject: [PATCH 089/190] msg > env.msg --- Jenkinsfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 82a019e17..7e8514ae4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,20 +23,20 @@ pipeline { post { always { script { - def msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" - msg += "**Branch:** ${env.GIT_LOCAL_BRANCH}\n" - msg += "**Changes:** \n" + env.msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" + env.msg += "**Branch:** ${env.GIT_LOCAL_BRANCH}\n" + env.msg += "**Changes:** \n" if (!currentBuild.changeSets.isEmpty()) { currentBuild.changeSets.first().getLogs().each { - msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + "*\n" + env.msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + "*\n" } } else { - msg += "no changes for this run\n" + env.msg += "no changes for this run\n" } - if (msg.length() > 1024) msg.take(msg.length() - 1024) + if (env.msg.length() > 1024) env.msg.take(env.msg.length() - 1024) } withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "${msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.GIT_LOCAL_BRANCH} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL + discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.GIT_LOCAL_BRANCH} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL } } cleanup { From b33bcff172f685a9b03eff7036617c38b15dcb88 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 15:09:38 -0500 Subject: [PATCH 090/190] run a script from google to fix migrations --- leagues/migrations/0001_initial.py | 88 +++++----- leagues/migrations/0002_auto_20201209_1508.py | 85 ++++++++++ .../0002_leaguesettings_allow_fa.py | 18 -- leagues/migrations/0017_auto_20200901_1921.py | 38 ----- matches/migrations/0001_initial.py | 105 ++++-------- matches/migrations/0002_auto_20201209_1508.py | 154 ++++++++++++++++++ matches/models.py | 2 +- news/migrations/0001_initial.py | 2 +- pages/migrations/0001_initial.py | 4 +- profiles/migrations/0001_initial.py | 24 ++- .../migrations/0002_auto_20201201_2120.py | 29 ---- .../migrations/0012_auto_20200813_1639.py | 32 ---- profiles/migrations/0013_notification_pk1.py | 18 -- .../migrations/0014_auto_20200901_1908.py | 23 --- singletournaments/migrations/0001_initial.py | 6 +- store/migrations/0001_initial.py | 2 +- support/migrations/0001_initial.py | 2 +- teams/migrations/0001_initial.py | 17 +- teams/migrations/0002_auto_20201121_2203.py | 24 --- .../0003_delete_captainmembership.py | 16 -- teams/migrations/0009_auto_20200822_1657.py | 37 ----- teams/migrations/0010_auto_20200826_1818.py | 22 --- wagers/migrations/0001_initial.py | 4 +- 23 files changed, 347 insertions(+), 405 deletions(-) create mode 100644 leagues/migrations/0002_auto_20201209_1508.py delete mode 100644 leagues/migrations/0002_leaguesettings_allow_fa.py delete mode 100644 leagues/migrations/0017_auto_20200901_1921.py create mode 100644 matches/migrations/0002_auto_20201209_1508.py delete mode 100644 profiles/migrations/0002_auto_20201201_2120.py delete mode 100644 profiles/migrations/0012_auto_20200813_1639.py delete mode 100644 profiles/migrations/0013_notification_pk1.py delete mode 100644 profiles/migrations/0014_auto_20200901_1908.py delete mode 100644 teams/migrations/0002_auto_20201121_2203.py delete mode 100644 teams/migrations/0003_delete_captainmembership.py delete mode 100644 teams/migrations/0009_auto_20200822_1657.py delete mode 100644 teams/migrations/0010_auto_20200826_1818.py diff --git a/leagues/migrations/0001_initial.py b/leagues/migrations/0001_initial.py index 7f1603ce5..6a8be0485 100644 --- a/leagues/migrations/0001_initial.py +++ b/leagues/migrations/0001_initial.py @@ -1,7 +1,6 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:59 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -9,12 +8,47 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('matches', '0001_initial'), - ('teams', '0001_initial'), - ('singletournaments', '0001_initial'), ] operations = [ + migrations.CreateModel( + name='League', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='League Name', max_length=50)), + ('active', models.BooleanField(default=False)), + ('info', models.TextField(default='No information provided')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('image', models.ImageField(blank=True, null=True, upload_to='league_images')), + ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), + ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), + ('allow_register', models.BooleanField(default=False)), + ('open_register', models.DateTimeField()), + ('close_register', models.DateTimeField()), + ('start', models.DateTimeField()), + ('req_credits', models.PositiveSmallIntegerField(default=0)), + ('size', models.PositiveSmallIntegerField(default=8)), + ('disable_userreport', models.BooleanField(default=False)), + ('prize1', models.CharField(default='no prize specified', max_length=50)), + ('prize2', models.CharField(default='no prize specified', max_length=50)), + ('prize3', models.CharField(default='no prize specified', max_length=50)), + ], + ), + migrations.CreateModel( + name='LeagueDivision', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, null=True)), + ], + ), + migrations.CreateModel( + name='LeagueFreeAgent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(default='Include information about Free Agent here')), + ], + ), migrations.CreateModel( name='LeagueSettings', fields=[ @@ -39,6 +73,7 @@ class Migration(migrations.Migration): ('require_lol', models.BooleanField(default=False)), ('require_battlenet', models.BooleanField(default=False)), ('require_activision', models.BooleanField(default=False)), + ('allow_fa', models.BooleanField(default=False)), ], ), migrations.CreateModel( @@ -50,48 +85,7 @@ class Migration(migrations.Migration): ('ot_losses', models.PositiveSmallIntegerField(default=0)), ('ot_wins', models.PositiveSmallIntegerField(default=0)), ('ties', models.PositiveSmallIntegerField(default=0)), - ('team', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_team', to='teams.Team')), - ], - ), - migrations.CreateModel( - name='LeagueDivision', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, null=True)), - ('matches', models.ManyToManyField(blank=True, to='matches.Match')), - ('teams', models.ManyToManyField(blank=True, to='leagues.LeagueTeam')), - ], - ), - migrations.CreateModel( - name='League', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='League Name', max_length=50)), - ('active', models.BooleanField(default=False)), - ('info', models.TextField(default='No information provided')), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('image', models.ImageField(blank=True, null=True, upload_to='league_images')), - ('teamformat', models.SmallIntegerField(choices=[(0, '1v1'), (1, '2v2'), (2, '3v3'), (3, '4v4'), (4, '5v5'), (5, '6v6')], default=1)), - ('bestof', models.SmallIntegerField(choices=[(0, 'Best of 1'), (1, 'Best of 3'), (2, 'Best of 5'), (3, 'Best of 7'), (4, 'Best of 9')], default=0)), - ('allow_register', models.BooleanField(default=False)), - ('open_register', models.DateTimeField()), - ('close_register', models.DateTimeField()), - ('start', models.DateTimeField()), - ('req_credits', models.PositiveSmallIntegerField(default=0)), - ('size', models.PositiveSmallIntegerField(default=8)), - ('disable_userreport', models.BooleanField(default=False)), - ('prize1', models.CharField(default='no prize specified', max_length=50)), - ('prize2', models.CharField(default='no prize specified', max_length=50)), - ('prize3', models.CharField(default='no prize specified', max_length=50)), - ('divisions', models.ManyToManyField(blank=True, to='leagues.LeagueDivision')), - ('game', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_game', to='matches.GameChoice')), - ('maps', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_maps', to='matches.MapPoolChoice')), - ('platform', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_platform', to='matches.PlatformChoice')), - ('ruleset', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_ruleset', to='singletournaments.SingleTournamentRuleset')), - ('settings', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_settings', to='leagues.LeagueSettings')), - ('sport', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_sport', to='matches.SportChoice')), - ('teams', models.ManyToManyField(blank=True, to='leagues.LeagueTeam')), + ('points', models.PositiveIntegerField(default=0)), ], ), ] diff --git a/leagues/migrations/0002_auto_20201209_1508.py b/leagues/migrations/0002_auto_20201209_1508.py new file mode 100644 index 000000000..86e566d05 --- /dev/null +++ b/leagues/migrations/0002_auto_20201209_1508.py @@ -0,0 +1,85 @@ +# Generated by Django 2.2.15 on 2020-12-09 20:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('leagues', '0001_initial'), + ('matches', '0002_auto_20201209_1508'), + ('teams', '0001_initial'), + ('profiles', '0001_initial'), + ('singletournaments', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='leagueteam', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_team', to='teams.Team'), + ), + migrations.AddField( + model_name='leaguefreeagent', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fa_profile', to='profiles.UserProfile'), + ), + migrations.AddField( + model_name='leaguedivision', + name='matches', + field=models.ManyToManyField(blank=True, to='matches.Match'), + ), + migrations.AddField( + model_name='leaguedivision', + name='teams', + field=models.ManyToManyField(blank=True, to='leagues.LeagueTeam'), + ), + migrations.AddField( + model_name='league', + name='divisions', + field=models.ManyToManyField(blank=True, to='leagues.LeagueDivision'), + ), + migrations.AddField( + model_name='league', + name='fa', + field=models.ManyToManyField(blank=True, related_name='league_fas', to='leagues.LeagueFreeAgent'), + ), + migrations.AddField( + model_name='league', + name='game', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_game', to='matches.GameChoice'), + ), + migrations.AddField( + model_name='league', + name='maps', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_maps', to='matches.MapPoolChoice'), + ), + migrations.AddField( + model_name='league', + name='platform', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_platform', to='matches.PlatformChoice'), + ), + migrations.AddField( + model_name='league', + name='ruleset', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_ruleset', to='singletournaments.SingleTournamentRuleset'), + ), + migrations.AddField( + model_name='league', + name='settings', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='league_settings', to='leagues.LeagueSettings'), + ), + migrations.AddField( + model_name='league', + name='sport', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='league_sport', to='matches.SportChoice'), + ), + migrations.AddField( + model_name='league', + name='teams', + field=models.ManyToManyField(blank=True, to='leagues.LeagueTeam'), + ), + ] diff --git a/leagues/migrations/0002_leaguesettings_allow_fa.py b/leagues/migrations/0002_leaguesettings_allow_fa.py deleted file mode 100644 index 50d97b7a4..000000000 --- a/leagues/migrations/0002_leaguesettings_allow_fa.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-12-02 03:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('leagues', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='leaguesettings', - name='allow_fa', - field=models.BooleanField(default=False), - ), - ] diff --git a/leagues/migrations/0017_auto_20200901_1921.py b/leagues/migrations/0017_auto_20200901_1921.py deleted file mode 100644 index 96772a7d5..000000000 --- a/leagues/migrations/0017_auto_20200901_1921.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.15 on 2020-09-01 23:21 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0014_auto_20200901_1908'), - ('leagues', '0016_league_teams'), - ] - - operations = [ - migrations.AddField( - model_name='leaguesettings', - name='allow_fa', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='leagueteam', - name='points', - field=models.PositiveIntegerField(default=0), - ), - migrations.CreateModel( - name='LeagueFreeAgent', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.TextField(default='Include information about Free Agent here')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fa_profile', to='profiles.UserProfile')), - ], - ), - migrations.AddField( - model_name='league', - name='fa', - field=models.ManyToManyField(blank=True, related_name='league_fas', to='leagues.LeagueFreeAgent'), - ), - ] diff --git a/matches/migrations/0001_initial.py b/matches/migrations/0001_initial.py index 48dc7a49e..271ee924f 100644 --- a/matches/migrations/0001_initial.py +++ b/matches/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:58 +# Generated by Django 2.2.15 on 2020-12-09 20:08 -from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -10,8 +9,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('teams', '0001_initial'), ] operations = [ @@ -31,7 +28,16 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('map_num', models.IntegerField(blank=True, default=0, null=True)), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_for', to='matches.GameChoice')), + ], + ), + migrations.CreateModel( + name='MapPoolChoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='default map pool', max_length=255)), + ('description', models.CharField(default='No map pool description', max_length=255)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), ], ), migrations.CreateModel( @@ -51,16 +57,28 @@ class Migration(migrations.Migration): ('bye_1', models.BooleanField(default=False)), ('bye_2', models.BooleanField(default=False)), ('disable_userreport', models.BooleanField(default=True)), - ('awayteam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='awayteam', to='teams.Team')), - ('game', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='GameChoice', to='matches.GameChoice')), - ('hometeam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hometeam', to='teams.Team')), - ('loser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loser', to='teams.Team')), - ('map', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_map', to='matches.MapChoice')), ], options={ 'verbose_name_plural': 'matches', }, ), + migrations.CreateModel( + name='MatchCheckIn', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='MatchDispute', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('teamproof_1', models.URLField()), + ('teamproof_2', models.URLField(blank=True)), + ('teamproof_3', models.URLField(blank=True)), + ], + ), migrations.CreateModel( name='MatchStats', fields=[ @@ -167,73 +185,6 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(auto_now_add=True)), ('proof', models.CharField(default='no text inserted', max_length=300)), ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matchreporting', to='matches.Match')), - ('reported_winner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winnerreporting', to='teams.Team')), - ('reporting_team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teamreporting', to='teams.Team')), - ('reporting_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='userreporting', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='MatchDispute', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('teamproof_1', models.URLField()), - ('teamproof_2', models.URLField(blank=True)), - ('teamproof_3', models.URLField(blank=True)), - ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='disputedMatch', to='matches.Match')), - ('team1', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1', to='teams.Team')), - ('team1origreporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1OriginalReporter', to=settings.AUTH_USER_MODEL)), - ('team2', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2', to='teams.Team')), - ('team2origreporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2OriginalReporter', to=settings.AUTH_USER_MODEL)), - ('teamreporter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1Disputer', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='MatchCheckIn', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('match', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_checkin', to='matches.Match')), - ('players', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), - ('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkin_user', to=settings.AUTH_USER_MODEL)), - ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checking_in_team', to='teams.Team')), - ], - ), - migrations.AddField( - model_name='match', - name='platform', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='PlatformChoice', to='matches.PlatformChoice'), - ), - migrations.AddField( - model_name='match', - name='sport', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='SportChoice', to='matches.SportChoice'), - ), - migrations.AddField( - model_name='match', - name='team1reportedwinner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1reportedwinner', to='teams.Team'), - ), - migrations.AddField( - model_name='match', - name='team2reportedwinner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2reportedwinner', to='teams.Team'), - ), - migrations.AddField( - model_name='match', - name='winner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='champions', to='teams.Team'), - ), - migrations.CreateModel( - name='MapPoolChoice', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='default map pool', max_length=255)), - ('description', models.CharField(default='No map pool description', max_length=255)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_pool_for', to='matches.GameChoice')), - ('maps', models.ManyToManyField(blank=True, to='matches.MapChoice')), ], ), ] diff --git a/matches/migrations/0002_auto_20201209_1508.py b/matches/migrations/0002_auto_20201209_1508.py new file mode 100644 index 000000000..1d20a707c --- /dev/null +++ b/matches/migrations/0002_auto_20201209_1508.py @@ -0,0 +1,154 @@ +# Generated by Django 2.2.15 on 2020-12-09 20:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teams', '0001_initial'), + ('matches', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='matchreport', + name='reported_winner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winnerreporting', to='teams.Team'), + ), + migrations.AddField( + model_name='matchreport', + name='reporting_team', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teamreporting', to='teams.Team'), + ), + migrations.AddField( + model_name='matchreport', + name='reporting_user', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='userreporting', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchdispute', + name='match', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='disputedMatch', to='matches.Match'), + ), + migrations.AddField( + model_name='matchdispute', + name='team1', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1', to='teams.Team'), + ), + migrations.AddField( + model_name='matchdispute', + name='team1origreporter', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1OriginalReporter', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchdispute', + name='team2', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2', to='teams.Team'), + ), + migrations.AddField( + model_name='matchdispute', + name='team2origreporter', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2OriginalReporter', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchdispute', + name='teamreporter', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1Disputer', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchcheckin', + name='match', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_checkin', to='matches.Match'), + ), + migrations.AddField( + model_name='matchcheckin', + name='players', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchcheckin', + name='reporter', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checkin_user', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='matchcheckin', + name='team', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checking_in_team', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='awayteam', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='awayteam', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='game', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='GameChoice', to='matches.GameChoice'), + ), + migrations.AddField( + model_name='match', + name='hometeam', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hometeam', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='loser', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loser', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='map', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='match_map', to='matches.MapChoice'), + ), + migrations.AddField( + model_name='match', + name='maps', + field=models.ManyToManyField(to='matches.MapPoolChoice'), + ), + migrations.AddField( + model_name='match', + name='platform', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='PlatformChoice', to='matches.PlatformChoice'), + ), + migrations.AddField( + model_name='match', + name='sport', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='SportChoice', to='matches.SportChoice'), + ), + migrations.AddField( + model_name='match', + name='team1reportedwinner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team1reportedwinner', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='team2reportedwinner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='team2reportedwinner', to='teams.Team'), + ), + migrations.AddField( + model_name='match', + name='winner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='champions', to='teams.Team'), + ), + migrations.AddField( + model_name='mappoolchoice', + name='game', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_pool_for', to='matches.GameChoice'), + ), + migrations.AddField( + model_name='mappoolchoice', + name='maps', + field=models.ManyToManyField(blank=True, to='matches.MapChoice'), + ), + migrations.AddField( + model_name='mapchoice', + name='game', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='map_for', to='matches.GameChoice'), + ), + ] diff --git a/matches/models.py b/matches/models.py index ac749e48e..7291e24f5 100644 --- a/matches/models.py +++ b/matches/models.py @@ -143,7 +143,7 @@ class Match(models.Model): type = models.CharField(blank=True, null=True, max_length=20) matchnum = models.SmallIntegerField(default=0) #TODO add mtm field for maps - for cases that it is more then a BO1 - #maps = models.ManyToManyField(MapChoice, related_name='match_maps', on_delete=models.SET_NULL, null=True) + maps = models.ManyToManyField(MapPoolChoice) map = models.ForeignKey(MapChoice, related_name='match_map', on_delete=models.SET_NULL, null=True) game = models.ForeignKey(GameChoice, related_name='GameChoice', on_delete=models.PROTECT, null=True) # default to ps4 for now bc why not diff --git a/news/migrations/0001_initial.py b/news/migrations/0001_initial.py index 6980f5480..2430929b0 100644 --- a/news/migrations/0001_initial.py +++ b/news/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:58 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models diff --git a/pages/migrations/0001_initial.py b/pages/migrations/0001_initial.py index fec58b425..094283374 100644 --- a/pages/migrations/0001_initial.py +++ b/pages/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:58 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.db import migrations, models import django.db.models.deletion @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('singletournaments', '__first__'), + ('singletournaments', '0001_initial'), ] operations = [ diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py index 8807c515a..57f361e93 100644 --- a/profiles/migrations/0001_initial.py +++ b/profiles/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:58 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models @@ -12,9 +12,25 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teams', '0001_initial'), ] operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datetime', models.DateTimeField(auto_created=True)), + ('title', models.CharField(blank=True, max_length=255, null=True)), + ('description', models.TextField(default='No description given')), + ('sender', models.CharField(default='System', max_length=255)), + ('type', models.CharField(choices=[('match', 1), ('tournament', 2), ('league', 3), ('team', 4), ('support', 5), ('news', 6), ('general', 7), ('store', 8)], default='general', max_length=255)), + ('link', models.CharField(max_length=255)), + ('pk1', models.IntegerField(default=0)), + ('read', models.BooleanField(default=False)), + ('seen', models.BooleanField(default=False)), + ], + ), migrations.CreateModel( name='UserProfile', fields=[ @@ -25,6 +41,8 @@ class Migration(migrations.Migration): ('total_earning', models.PositiveSmallIntegerField(default=0)), ('current_earning', models.PositiveSmallIntegerField(default=0)), ('about_me', models.TextField(blank=True, default='Forever a mystery')), + ('steamid64', models.CharField(blank=True, default='No SteamID64', max_length=255)), + ('discord', models.CharField(blank=True, default='No Dsicord', max_length=255)), ('xbl', models.CharField(blank=True, default='No Xbox Live Linked', max_length=30)), ('psn', models.CharField(blank=True, default='No PSN Linked', max_length=30)), ('steam', models.CharField(blank=True, default='No Steam Linked', max_length=30)), @@ -56,6 +74,10 @@ class Migration(migrations.Migration): ('rank', models.PositiveSmallIntegerField(default=100)), ('country', django_countries.fields.CountryField(default='US', max_length=2)), ('email_enabled', models.BooleanField(default=True)), + ('captain_teams', models.ManyToManyField(related_name='profile_captain_teams', to='teams.Team')), + ('founder_teams', models.ManyToManyField(related_name='profile_founder_teams', to='teams.Team')), + ('notifications', models.ManyToManyField(blank=True, related_name='user_notifications', to='profiles.Notification')), + ('player_teams', models.ManyToManyField(related_name='profile_player_teams', to='teams.Team')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), ], ), diff --git a/profiles/migrations/0002_auto_20201201_2120.py b/profiles/migrations/0002_auto_20201201_2120.py deleted file mode 100644 index 74288966d..000000000 --- a/profiles/migrations/0002_auto_20201201_2120.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.12 on 2020-12-02 02:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0002_auto_20201121_2203'), - ('profiles', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='captain_teams', - field=models.ManyToManyField(related_name='profile_captain_teams', to='teams.Team'), - ), - migrations.AddField( - model_name='userprofile', - name='founder_teams', - field=models.ManyToManyField(related_name='profile_founder_teams', to='teams.Team'), - ), - migrations.AddField( - model_name='userprofile', - name='player_teams', - field=models.ManyToManyField(related_name='profile_player_teams', to='teams.Team'), - ), - ] diff --git a/profiles/migrations/0012_auto_20200813_1639.py b/profiles/migrations/0012_auto_20200813_1639.py deleted file mode 100644 index 08aa2844e..000000000 --- a/profiles/migrations/0012_auto_20200813_1639.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 2.2.14 on 2020-08-13 20:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0011_userprofile_activisionid'), - ] - - operations = [ - migrations.CreateModel( - name='Notification', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('datetime', models.DateTimeField(auto_created=True)), - ('title', models.CharField(blank=True, max_length=255, null=True)), - ('description', models.TextField(default='No description given')), - ('sender', models.CharField(default='System', max_length=255)), - ('type', models.CharField(choices=[('match', 1), ('tournament', 2), ('league', 3), ('team', 4), ('support', 5), ('news', 6), ('general', 7), ('store', 8)], default='general', max_length=255)), - ('link', models.CharField(max_length=255)), - ('read', models.BooleanField(default=False)), - ('seen', models.BooleanField(default=False)), - ], - ), - migrations.AddField( - model_name='userprofile', - name='notifications', - field=models.ManyToManyField(blank=True, related_name='user_notifications', to='profiles.Notification'), - ), - ] diff --git a/profiles/migrations/0013_notification_pk1.py b/profiles/migrations/0013_notification_pk1.py deleted file mode 100644 index 4a0e47a6b..000000000 --- a/profiles/migrations/0013_notification_pk1.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.15 on 2020-08-27 22:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0012_auto_20200813_1639'), - ] - - operations = [ - migrations.AddField( - model_name='notification', - name='pk1', - field=models.IntegerField(default=0), - ), - ] diff --git a/profiles/migrations/0014_auto_20200901_1908.py b/profiles/migrations/0014_auto_20200901_1908.py deleted file mode 100644 index 378882db4..000000000 --- a/profiles/migrations/0014_auto_20200901_1908.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.15 on 2020-09-01 23:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('profiles', '0013_notification_pk1'), - ] - - operations = [ - migrations.AddField( - model_name='userprofile', - name='discord', - field=models.CharField(blank=True, default='No Dsicord', max_length=255), - ), - migrations.AddField( - model_name='userprofile', - name='steamid64', - field=models.CharField(blank=True, default='No SteamID64', max_length=255), - ), - ] diff --git a/singletournaments/migrations/0001_initial.py b/singletournaments/migrations/0001_initial.py index ab03ffdf0..c81ee4d2f 100644 --- a/singletournaments/migrations/0001_initial.py +++ b/singletournaments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:59 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models @@ -10,9 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('teams', '0001_initial'), - ('matches', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('teams', '0001_initial'), + ('matches', '0002_auto_20201209_1508'), ] operations = [ diff --git a/store/migrations/0001_initial.py b/store/migrations/0001_initial.py index 3d61e1e2c..64f9a65a0 100644 --- a/store/migrations/0001_initial.py +++ b/store/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:59 +# Generated by Django 2.2.15 on 2020-12-09 20:08 import django.core.validators from django.db import migrations, models diff --git a/support/migrations/0001_initial.py b/support/migrations/0001_initial.py index 8f4bbda90..9638f0927 100644 --- a/support/migrations/0001_initial.py +++ b/support/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:59 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models diff --git a/teams/migrations/0001_initial.py b/teams/migrations/0001_initial.py index f8cd0bd90..723b3cc2a 100644 --- a/teams/migrations/0001_initial.py +++ b/teams/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:58 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models @@ -12,6 +12,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('matches', '0001_initial'), ] operations = [ @@ -39,7 +40,9 @@ class Migration(migrations.Migration): ('image', models.ImageField(blank=True, upload_to='team_images')), ('captain', models.ManyToManyField(related_name='teamcaptain', to=settings.AUTH_USER_MODEL)), ('founder', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='founder', to=settings.AUTH_USER_MODEL)), + ('matches', models.ManyToManyField(related_name='team_matches', to='matches.Match')), ('players', models.ManyToManyField(related_name='teamplayers', to=settings.AUTH_USER_MODEL)), + ('team_stat', models.ManyToManyField(related_name='match_team_stat', to='matches.TeamMatchStats')), ], options={ 'verbose_name': 'Team', @@ -52,23 +55,13 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('expire', models.DateTimeField()), - ('captain', models.CharField(choices=[('captain', 'Captain'), ('player', 'Player')], default='player', max_length=20)), + ('captain', models.BooleanField(default=False)), ('accepted', models.BooleanField(default=False)), ('declined', models.BooleanField(default=False)), ('active', models.BooleanField(default=True)), - ('hasPerms', models.BooleanField(default=False)), ('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frominvite', to=settings.AUTH_USER_MODEL)), ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitedto', to='teams.Team')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='toinvite', to=settings.AUTH_USER_MODEL)), ], ), - migrations.CreateModel( - name='CaptainMembership', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('active', models.BooleanField(default=True)), - ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team', to='teams.Team')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='captainperson', to=settings.AUTH_USER_MODEL)), - ], - ), ] diff --git a/teams/migrations/0002_auto_20201121_2203.py b/teams/migrations/0002_auto_20201121_2203.py deleted file mode 100644 index d2d7a936e..000000000 --- a/teams/migrations/0002_auto_20201121_2203.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.15 on 2020-11-22 03:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('matches', '0001_initial'), - ('teams', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='team', - name='matches', - field=models.ManyToManyField(related_name='team_matches', to='matches.Match'), - ), - migrations.AddField( - model_name='team', - name='team_stat', - field=models.ManyToManyField(related_name='match_team_stat', to='matches.TeamMatchStats'), - ), - ] diff --git a/teams/migrations/0003_delete_captainmembership.py b/teams/migrations/0003_delete_captainmembership.py deleted file mode 100644 index f5b980ece..000000000 --- a/teams/migrations/0003_delete_captainmembership.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.2.12 on 2020-12-02 02:57 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0002_auto_20201121_2203'), - ] - - operations = [ - migrations.DeleteModel( - name='CaptainMembership', - ), - ] diff --git a/teams/migrations/0009_auto_20200822_1657.py b/teams/migrations/0009_auto_20200822_1657.py deleted file mode 100644 index 76865f4ed..000000000 --- a/teams/migrations/0009_auto_20200822_1657.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.2.14 on 2020-08-22 20:57 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('teams', '0008_team_image'), - ] - - operations = [ - migrations.RemoveField( - model_name='team', - name='captain', - ), - migrations.AddField( - model_name='team', - name='captains', - field=models.ManyToManyField(blank=True, related_name='team_captains', to=settings.AUTH_USER_MODEL), - ), - migrations.RemoveField( - model_name='team', - name='players', - field=models.ManyToManyField(blank=True, related_name='team_players', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='team', - name='players', - field=models.ManyToManyField(blank=True, related_name='team_players', to=settings.AUTH_USER_MODEL), - ), - migrations.DeleteModel( - name='CaptainMembership', - ), - ] diff --git a/teams/migrations/0010_auto_20200826_1818.py b/teams/migrations/0010_auto_20200826_1818.py deleted file mode 100644 index d0e67b1c3..000000000 --- a/teams/migrations/0010_auto_20200826_1818.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.15 on 2020-08-26 22:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('teams', '0009_auto_20200822_1657'), - ] - - operations = [ - migrations.RemoveField( - model_name='teaminvite', - name='hasPerms', - ), - migrations.AlterField( - model_name='teaminvite', - name='captain', - field=models.BooleanField(default=False), - ), - ] diff --git a/wagers/migrations/0001_initial.py b/wagers/migrations/0001_initial.py index 501141dd4..13101a441 100644 --- a/wagers/migrations/0001_initial.py +++ b/wagers/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.15 on 2020-11-17 16:59 +# Generated by Django 2.2.15 on 2020-12-09 20:08 from django.conf import settings from django.db import migrations, models @@ -11,8 +11,8 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('matches', '0001_initial'), ('teams', '0001_initial'), + ('matches', '0001_initial'), ] operations = [ From fa0c01620f2ae11826a63c055640fc97fb290c50 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 15:13:19 -0500 Subject: [PATCH 091/190] remove todo in match.models --- matches/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/matches/models.py b/matches/models.py index 7291e24f5..a5e7f8a20 100644 --- a/matches/models.py +++ b/matches/models.py @@ -142,7 +142,6 @@ def __str__(self): class Match(models.Model): type = models.CharField(blank=True, null=True, max_length=20) matchnum = models.SmallIntegerField(default=0) - #TODO add mtm field for maps - for cases that it is more then a BO1 maps = models.ManyToManyField(MapPoolChoice) map = models.ForeignKey(MapChoice, related_name='match_map', on_delete=models.SET_NULL, null=True) game = models.ForeignKey(GameChoice, related_name='GameChoice', on_delete=models.PROTECT, null=True) From b02f23efddfd544e0ebbd39472159f594809d731 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 15:13:35 -0500 Subject: [PATCH 092/190] pass map pool to tournaments matches when created --- singletournaments/models.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/singletournaments/models.py b/singletournaments/models.py index eef6f1877..ed78d80f5 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -247,7 +247,7 @@ def generate_bracket(self): # bye_1 specifies that the match has only 1 bye team in it, and that one team is being automatically # advanced to the next round m[1] = Match(game=game, matchnum=1, platform=platform, hometeam=team_seeds[0], - teamformat=teamformat, bestof=bestof, reported=True, + teamformat=teamformat, bestof=bestof, reported=True, maps=self.map_pool, completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, # disable user reports based on the tournament field value disable_userreport=True, sport=self.sport) @@ -268,7 +268,7 @@ def generate_bracket(self): # bye_2 specifies that both teams are bye teams, therefor a team will receive a bye in the first # and second round m[i] = Match(game=game, matchnum=i, platform=platform, - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, + teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, completed=True, bye_2=True, info=round1.info, disable_userreport=True) m[i].save() @@ -279,7 +279,7 @@ def generate_bracket(self): round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) m[1] = Match(game=game, matchnum=1, platform=platform, - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, + teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, completed=True, bye_2=True, info=round1.info, disable_userreport=True) m[1].save() @@ -298,7 +298,7 @@ def generate_bracket(self): round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) m[x] = Match(game=game, matchnum=x, platform=platform, hometeam=team_seeds[0], - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, + teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, disable_userreport=True) m[x].save() @@ -316,7 +316,7 @@ def generate_bracket(self): round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) m[x] = Match(game=game, platform=platform, hometeam=team_seeds[0], awayteam=team_seeds[-1], - teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, + teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, maps=self.map_pool, # this is an actual match, so disable user reports based on the field setting disable_userreport=self.disable_userreport) m[x].save() @@ -336,7 +336,7 @@ def generate_bracket(self): hometeam.save() round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - m[x] = Match(game=game, matchnum=x, platform=platform, hometeam=team_seeds[0], + m[x] = Match(game=game, matchnum=x, platform=platform, hometeam=team_seeds[0], maps=self.map_pool, teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, disable_userreport=True) @@ -355,7 +355,7 @@ def generate_bracket(self): round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) m[x] = Match(game=game, platform=platform, hometeam=team_seeds[0], awayteam=team_seeds[-1], - teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, + teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, maps=self.map_pool, disable_userreport=self.disable_userreport) m[x].save() round1.matches.add(m[x]) From 65f1c8cedaee50790866c32ff1a3027f14085e63 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 15:45:35 -0500 Subject: [PATCH 093/190] rewrite bracket generation, remove hard coded team sizes --- singletournaments/models.py | 264 +++++------------------------------- 1 file changed, 34 insertions(+), 230 deletions(-) diff --git a/singletournaments/models.py b/singletournaments/models.py index ed78d80f5..e15003548 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -132,239 +132,43 @@ def set_inactive(self, **kwargs): tournament.active = False tournament.save() - def generate_rounds(self): - # create the round objects based on the tournament size - if self.size == 4: - # generate 2 rounds - round1 = SingleTournamentRound(matchesnum=2, roundnum=1, tournament=self) - round1.save() - round2 = SingleTournamentRound(matchesnum=1, roundnum=2, tournament=self) - round2.save() - elif self.size == 8: - # generate 3 rounds - round1 = SingleTournamentRound(matchesnum=4, roundnum=1, tournament=self) - round2 = SingleTournamentRound(matchesnum=2, roundnum=2, tournament=self) - round3 = SingleTournamentRound(matchesnum=1, roundnum=3, tournament=self) - round1.save() - round2.save() - round3.save() - elif self.size == 16: - # generate 4 rounds - round1 = SingleTournamentRound(matchesnum=8, roundnum=1, tournament=self) - round2 = SingleTournamentRound(matchesnum=4, roundnum=2, tournament=self) - round3 = SingleTournamentRound(matchesnum=2, roundnum=3, tournament=self) - round4 = SingleTournamentRound(matchesnum=1, roundnum=4, tournament=self) - round1.save() - round2.save() - round3.save() - round4.save() - elif self.size == 32: - # generate 5 rounds - round1 = SingleTournamentRound(matchesnum=16, roundnum=1, tournament=self) - round2 = SingleTournamentRound(matchesnum=8, roundnum=2, tournament=self) - round3 = SingleTournamentRound(matchesnum=4, roundnum=3, tournament=self) - round4 = SingleTournamentRound(matchesnum=2, roundnum=4, tournament=self) - round5 = SingleTournamentRound(matchesnum=1, roundnum=5, tournament=self) - round1.save() - round2.save() - round3.save() - round4.save() - round5.save() - elif self.size == 64: - # generate 6 rounds - round1 = SingleTournamentRound(matchesnum=32, roundnum=1, tournament=self) - round2 = SingleTournamentRound(matchesnum=16, roundnum=2, tournament=self) - round3 = SingleTournamentRound(matchesnum=8, roundnum=3, tournament=self) - round4 = SingleTournamentRound(matchesnum=4, roundnum=4, tournament=self) - round5 = SingleTournamentRound(matchesnum=2, roundnum=5, tournament=self) - round6 = SingleTournamentRound(matchesnum=1, roundnum=6, tournament=self) - round1.save() - round2.save() - round3.save() - round4.save() - round5.save() - round6.save() - elif self.size == 128: - # generate 7 rounds - round1 = SingleTournamentRound(matchesnum=64, roundnum=1, tournament=self) - round2 = SingleTournamentRound(matchesnum=32, roundnum=2, tournament=self) - round3 = SingleTournamentRound(matchesnum=16, roundnum=3, tournament=self) - round4 = SingleTournamentRound(matchesnum=8, roundnum=4, tournament=self) - round5 = SingleTournamentRound(matchesnum=4, roundnum=5, tournament=self) - round6 = SingleTournamentRound(matchesnum=2, roundnum=6, tournament=self) - round7 = SingleTournamentRound(matchesnum=1, roundnum=7, tournament=self) - round1.save() - round2.save() - round3.save() - round4.save() - round5.save() - round6.save() - round7.save() - def generate_bracket(self): - # seed teams and make matches - game = self.game - platform = self.platform - teamformat = self.teamformat - bestof = self.bestof - size = self.size - numteams = self.teams.count() - bye = size - numteams - teams = list(self.teams.all()) - - for team in teams: - team.get_total_xp() - - count = 1 - seeds = [] - while count <= size: - seeds.append(count) - count += 1 - - team_seeds = [] - max_matches = size / 2 - if not self.xp_seed: - random.shuffle(teams) - else: - teams = self.teams.order_by('-totalxp') + teams = len(self.teams) + myteams = self.teams + round1 = SingleTournamentRound(tournament=self) + if teams % 2 == 0: + # no byes required - get 2 teams and make a match + while len(myteams) != 0: + temp1 = myteams.all().order_by("?").first() + temp2 = myteams.all().order_by("?").first() + tempmatch = Match(awayteam=temp1, hometeam=temp2, maps=self.map_pool, game=self.game, platform=self.platform) + tempmatch.save() + myteams.remove(temp1) + myteams.remove(temp2) + round1.matches.add(tempmatch) - for i in teams: - team_seeds.append(i) - - m = dict() - if bye == 2 or bye == 1: - offset = 0 - else: - offset = -2 - skipotherbye = False - - if bye >= 3: - if bye % 2 != 0: - hometeam = team_seeds[0] - hometeam.seeds = seeds[0] - hometeam.save() - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - # bye_1 specifies that the match has only 1 bye team in it, and that one team is being automatically - # advanced to the next round - m[1] = Match(game=game, matchnum=1, platform=platform, hometeam=team_seeds[0], - teamformat=teamformat, bestof=bestof, reported=True, maps=self.map_pool, - completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, - # disable user reports based on the tournament field value - disable_userreport=True, sport=self.sport) - m[1].save() - - round1.matches.add(m[1]) - round1.save() - del team_seeds[0] - del seeds[0] - - bye -= 1 - - if bye % 2 == 0: - byematches = bye / 2 - - for i in range(1, int(byematches) + 1): - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - # bye_2 specifies that both teams are bye teams, therefor a team will receive a bye in the first - # and second round - m[i] = Match(game=game, matchnum=i, platform=platform, - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, - completed=True, bye_2=True, info=round1.info, disable_userreport=True) - m[i].save() - - round1.matches.add(m[i]) - round1.save() - skipotherbye = True - elif bye == 2 and not skipotherbye: - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - - m[1] = Match(game=game, matchnum=1, platform=platform, - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, - completed=True, bye_2=True, info=round1.info, disable_userreport=True) - m[1].save() - - round1.matches.add(m[1]) - round1.save() - if bye != 0: - for x in range(bye + 1, int(max_matches) + bye + offset): - - if len(team_seeds) == 0: - break - - if len(team_seeds) == 1: - hometeam = team_seeds[0] - hometeam.seeds = seeds[0] - hometeam.save() - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - - m[x] = Match(game=game, matchnum=x, platform=platform, hometeam=team_seeds[0], - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, maps=self.map_pool, - completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, - disable_userreport=True) - m[x].save() - - round1.matches.add(m[x]) - round1.save() - break - - hometeam = team_seeds[0] - hometeam.seed = seeds[0] - hometeam.save() - awayteam = team_seeds[-1] - awayteam.seed = seeds[-1] - awayteam.save() - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - - m[x] = Match(game=game, platform=platform, hometeam=team_seeds[0], awayteam=team_seeds[-1], - teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, maps=self.map_pool, - # this is an actual match, so disable user reports based on the field setting - disable_userreport=self.disable_userreport) - m[x].save() - round1.matches.add(m[x]) - round1.save() - - del team_seeds[0] - del team_seeds[-1] - del seeds[0] - del seeds[-1] else: - for x in range(1, int(max_matches) + 1): - - if len(team_seeds) == 1: - hometeam = team_seeds[0] - hometeam.seeds = seeds[0] - hometeam.save() - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - - m[x] = Match(game=game, matchnum=x, platform=platform, hometeam=team_seeds[0], maps=self.map_pool, - teamformat=teamformat, bestof=bestof, reported=True, sport=self.sport, - completed=True, winner=team_seeds[0], bye_1=True, info=round1.info, - disable_userreport=True) - m[x].save() - - round1.matches.add(m[x]) - round1.save() - break - - hometeam = team_seeds[0] - hometeam.seed = seeds[0] - hometeam.save() - awayteam = team_seeds[-1] - awayteam.seed = seeds[-1] - awayteam.save() - round1 = SingleTournamentRound.objects.get(tournament=self, roundnum=1) - - m[x] = Match(game=game, platform=platform, hometeam=team_seeds[0], awayteam=team_seeds[-1], - teamformat=teamformat, bestof=bestof, info=round1.info, sport=self.sport, maps=self.map_pool, - disable_userreport=self.disable_userreport) - m[x].save() - round1.matches.add(m[x]) - round1.save() - - del team_seeds[0] - del team_seeds[-1] - del seeds[0] - del seeds[-1] + # take the first team and give them a bye + # TODO: verify this randomly grabs a random team + bteam = self.teams.all.order_by("?").first() + bmatch = Match(hometeam=None, bye_1=True, awayteam=bteam, winner=bteam, completed=True, + type='singletournament', maps=self.map_pool, game=self.game, platform=self.platform) + bmatch.save() + round1.matches.add(bmatch) + myteams.remove(bteam) + if len(myteams) % 2 != 0: + print("ITS BROKEN YOU SUCK") + return + while len(myteams) != 0: + temp1 = myteams.all().order_by("?").first() + temp2 = myteams.all().order_by("?").first() + tempmatch = Match(awayteam=temp1, hometeam=temp2, maps=self.map_pool, game=self.game, platform=self.platform) + tempmatch.save() + myteams.remove(temp1) + myteams.remove(temp2) + round1.matches.add(tempmatch) + + round1.save() def get_round1_byes(self, **kwargs): # only used for round 1 purposes From 7e647ce1f8436885adf3022ad85401c038ea5541 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:02:33 -0500 Subject: [PATCH 094/190] fix comparison warnings --- staff/views/singletournaments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/staff/views/singletournaments.py b/staff/views/singletournaments.py index f4efc0714..59f3d6a1f 100644 --- a/staff/views/singletournaments.py +++ b/staff/views/singletournaments.py @@ -283,11 +283,11 @@ def advance(request, pk): i = 0 while i < len(winners): - if winners[i] is 'BYE TEAM': + if winners[i] == 'BYE TEAM': # disable user reports, its a bye match newmatch = Match(game=tournament.game, platform=tournament.platform, hometeam=winners[i + 1], disable_userreport=True, sport=tournament.sport) - elif winners[i + 1] is 'BYE TEAM': + elif winners[i + 1] == 'BYE TEAM': # disable user reports, its a bye match newmatch = Match(game=tournament.game, platform=tournament.platform, sport=tournament.sport, awayteam=winners[i], disable_userreport=True) From 93e51cb3228b484fc00cad1c09df191affefc2d0 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:03:00 -0500 Subject: [PATCH 095/190] rename bracket4 to bracket, single template for all tournament brackets --- .../singletournament_bracket.html | 70 +++++++++ .../singletournament_bracket4.html | 142 ------------------ 2 files changed, 70 insertions(+), 142 deletions(-) create mode 100644 project-templates/singletournaments/singletournament_bracket.html delete mode 100644 project-templates/singletournaments/singletournament_bracket4.html diff --git a/project-templates/singletournaments/singletournament_bracket.html b/project-templates/singletournaments/singletournament_bracket.html new file mode 100644 index 000000000..853e90030 --- /dev/null +++ b/project-templates/singletournaments/singletournament_bracket.html @@ -0,0 +1,70 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + {{ tournament.name }} Bracket - {{ SITE_NAME }} +{% endblock %} + +{% block body %} + +
+
+
+ +
+ +
+
+
+
+ {% for round in rounds %} + + {% endfor %} +
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/project-templates/singletournaments/singletournament_bracket4.html b/project-templates/singletournaments/singletournament_bracket4.html deleted file mode 100644 index e777f4f61..000000000 --- a/project-templates/singletournaments/singletournament_bracket4.html +++ /dev/null @@ -1,142 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block head %} - {{ tournament.name }} Bracket - {{ SITE_NAME }} -{% endblock %} - -{% block body %} - -
-
-
-
-
-
- -
- -
-
-
-
- - -
    - -
  •  
  • - - {% if round2matches.count == 0 %} - -
  • TBD
  • -
  •  
  • -
  • TBD
  • - -
  • - - {% else %} - {% for match2 in round2matches %} - -
  • {{ match2.awayteam }} - {% if match2.winner.id == match2.awayteam.id %} - 1 - {% else %} - 0 - {% endif %} -
  • -
  •  
  • -
  • {{ match2.hometeam }} - {% if match2.winner.id == match2.hometeam.id %} - 1 - {% else %} - 0 - {% endif %} -
  • -
  • - {% endfor %} - - {% endif %} - -
- -
-
-
-
-
- -{% endblock %} \ No newline at end of file From 1507d0f9407c9cb5c5213a67a0a6c708ec931674 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:03:20 -0500 Subject: [PATCH 096/190] call .all() not manyrelatedmanager --- singletournaments/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/singletournaments/models.py b/singletournaments/models.py index e15003548..dd3d2d9e2 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -133,8 +133,8 @@ def set_inactive(self, **kwargs): tournament.save() def generate_bracket(self): - teams = len(self.teams) - myteams = self.teams + teams = len(self.teams.all()) + myteams = self.teams.all() round1 = SingleTournamentRound(tournament=self) if teams % 2 == 0: # no byes required - get 2 teams and make a match From d6b97e3e40c807119df04c47bfdc37398afb0a85 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:04:39 -0500 Subject: [PATCH 097/190] tweak and remove old views for non hard coded based sizes --- singletournaments/views.py | 143 +------------------------------------ 1 file changed, 3 insertions(+), 140 deletions(-) diff --git a/singletournaments/views.py b/singletournaments/views.py index 2af84b2dc..11a7911ae 100644 --- a/singletournaments/views.py +++ b/singletournaments/views.py @@ -359,150 +359,13 @@ class SingleTournamentBracket(View): def get(self, request, **kwargs): pk = self.kwargs['pk'] - winners = [] - completed = [] - doing = [] - matches = [] tournament = SingleEliminationTournament.objects.get(id=pk) teams = tournament.teams.all() if tournament.bracket_generated: # show the right bracket - if tournament.size == 4: - # get 2 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket4.html' - - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, - 'round2': round2, 'round1matches': round1matches, 'round2matches': round2matches}) - - elif tournament.size == 8: - # get 3 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket8.html' - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - round3 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=3) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - round3matches = round3.matches.all().order_by('id') - - for match in round1matches: - if match.winner is not None: - matchid = match.id - winner = match.winner.id - completed.append(matchid) - winners.append(winner) - matches.append(matchid) - # there is a winner, celebrate - else: - # do some shit - matchid = match.id - matches.append(matchid) - doing.append(matchid) - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, 'round2': round2, - 'round3': round3, 'round1matches': round1matches, 'round2matches': round2matches, - 'round3matches': round3matches}) - - elif tournament.size == 16: - # get 4 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket16.html' - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - round3 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=3) - round4 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=4) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - round3matches = round3.matches.all().order_by('id') - round4matches = round4.matches.all().order_by('id') - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, 'round2': round2, - 'round3': round3, 'round4': round4, 'round1matches': round1matches, - 'round2matches': round2matches, 'round3matches': round3matches, - 'round4matches': round4matches}) - - elif tournament.size == 32: - # get 5 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket32.html' - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - round3 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=3) - round4 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=4) - round5 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=5) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - round3matches = round3.matches.all().order_by('id') - round4matches = round4.matches.all().order_by('id') - round5matches = round5.matches.all().order_by('id') - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, 'round2': round2, - 'round3': round3, 'round4': round4, 'round5': round5, - 'round1matches': round1matches, 'round2matches': round2matches, - 'round3matches': round3matches, 'round4matches': round4matches, - 'round5matches': round5matches}) - - elif tournament.size == 64: - # get 6 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket64.html' - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - round3 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=3) - round4 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=4) - round5 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=5) - round6 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=6) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - round3matches = round3.matches.all().order_by('id') - round4matches = round4.matches.all().order_by('id') - round5matches = round5.matches.all().order_by('id') - round6matches = round6.matches.all().order_by('id') - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, 'round2': round2, - 'round3': round3, 'round4': round4, 'round5': round5, 'round6': round6, - 'round1matches': round1matches, 'round2matches': round2matches, - 'round3matches': round3matches, 'round4matches': round4matches, - 'round5matches': round5matches, 'round6matches': round6matches}) - - elif tournament.size == 128: - # get 7 rounds to pass to the view - template_name = 'singletournaments/singletournament_bracket128.html' - round1 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=1) - round2 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=2) - round3 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=3) - round4 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=4) - round5 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=5) - round6 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=6) - round7 = SingleTournamentRound.objects.get(tournament=tournament, roundnum=7) - - round1matches = round1.matches.all().order_by('id') - round2matches = round2.matches.all().order_by('id') - round3matches = round3.matches.all().order_by('id') - round4matches = round4.matches.all().order_by('id') - round5matches = round5.matches.all().order_by('id') - round6matches = round6.matches.all().order_by('id') - round7matches = round7.matches.all().order_by('id') - - return render(request, template_name, - {'x': pk, 'tournament': tournament, 'teams': teams, 'round1': round1, 'round2': round2, - 'round3': round3, 'round4': round4, 'round5': round5, 'round6': round6, 'round7': round7, - 'round1mathces': round1matches, 'round2matches': round2matches, - 'round3matches': round3matches, - 'round4matches': round4matches, 'round5matches': round5matches, - 'round6matches': round6matches, - 'round7matches': round7matches}) + rounds = SingleTournamentRound.objects.all().filter(tournament=tournament) + return render(request, 'singletournaments/singletournament_bracket.html', {'x':pk, 'tournament': tournament, + 'teams':teams, 'rounds': rounds}) else: # show some template that its not generated yet return render(request, 'singletournaments/no_bracket.html', From c1ee2b4f279733b47fd9fb7bf5a965a26ebac3a2 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:04:54 -0500 Subject: [PATCH 098/190] dont hard code round generation when launching tournament --- staff/views/singletournaments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staff/views/singletournaments.py b/staff/views/singletournaments.py index 59f3d6a1f..4ea94dced 100644 --- a/staff/views/singletournaments.py +++ b/staff/views/singletournaments.py @@ -145,7 +145,7 @@ def generate_bracket(request, pk): # Launch tournament return redirect('staff:tournamentlist') else: calculaterank() - tournament.generate_rounds() + #tournament.generate_rounds() tournament.generate_bracket() tournament.bracket_generated = True tournament.save() From f8f807671b6457020691760e7f09a442d9278234 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:05:59 -0500 Subject: [PATCH 099/190] remove old code from tournament views --- singletournaments/views.py | 113 ------------------------------------- 1 file changed, 113 deletions(-) diff --git a/singletournaments/views.py b/singletournaments/views.py index 11a7911ae..933590252 100644 --- a/singletournaments/views.py +++ b/singletournaments/views.py @@ -1,6 +1,4 @@ #import datetime - -#import pytz from django.contrib import messages from django.shortcuts import get_object_or_404 from django.shortcuts import render, redirect @@ -244,117 +242,6 @@ def get(self, request, pk): {'pk': pk, 'ruleset': ruleset}) -# class SingleTournamentMatchList(View): -# -# def get(self, request, **kwargs): -# pk = self.kwargs['pk'] -# tournament = SingleEliminationTournament.objects.get(id=pk) -# if tournament.size == 4: -# # get only 2 round objects, and the matches inside them. -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches}) -# -# elif tournament.size == 8: -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round3 = SingleTournamentRound.objects.get(roundnum=3, tournament=tournament) -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# round3matches = round3.matches.all().order_by('id') -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches, -# 'round3matches': round3matches}) -# -# # get 3 rounds -# elif tournament.size == 16: -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round3 = SingleTournamentRound.objects.get(roundnum=3, tournament=tournament) -# round4 = SingleTournamentRound.objects.get(roundnum=4, tournament=tournament) -# -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# round3matches = round3.matches.all().order_by('id') -# round4matches = round4.matches.all().order_by('id') -# -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches, -# 'round3matches': round3matches, 'round4matches': round4matches}) -# # get 4 rounds -# elif tournament.size == 32: -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round3 = SingleTournamentRound.objects.get(roundnum=3, tournament=tournament) -# round4 = SingleTournamentRound.objects.get(roundnum=4, tournament=tournament) -# round5 = SingleTournamentRound.objects.get(roundnum=5, tournament=tournament) -# -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# round3matches = round3.matches.all().order_by('id') -# round4matches = round4.matches.all().order_by('id') -# round5matches = round5.matches.all().order_by('id') -# -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches, -# 'round3matches': round3matches, 'round4matches': round4matches, -# 'round5matches': round5matches}) -# # get 5 rounds -# elif tournament.size == 64: -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round3 = SingleTournamentRound.objects.get(roundnum=3, tournament=tournament) -# round4 = SingleTournamentRound.objects.get(roundnum=4, tournament=tournament) -# round5 = SingleTournamentRound.objects.get(roundnum=5, tournament=tournament) -# round6 = SingleTournamentRound.objects.get(roundnum=6, tournament=tournament) -# -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# round3matches = round3.matches.all().order_by('id') -# round4matches = round4.matches.all().order_by('id') -# round5matches = round5.matches.all().order_by('id') -# round6matches = round6.matches.all().order_by('id') -# -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches, -# 'round3matches': round3matches, 'round4matches': round4matches, -# 'round5matches': round5matches, 'round6matches': round6matches}) -# # get 6 rounds -# elif tournament.size == 128: -# round1 = SingleTournamentRound.objects.get(roundnum=1, tournament=tournament) -# round2 = SingleTournamentRound.objects.get(roundnum=2, tournament=tournament) -# round3 = SingleTournamentRound.objects.get(roundnum=3, tournament=tournament) -# round4 = SingleTournamentRound.objects.get(roundnum=4, tournament=tournament) -# round5 = SingleTournamentRound.objects.get(roundnum=5, tournament=tournament) -# round6 = SingleTournamentRound.objects.get(roundnum=6, tournament=tournament) -# round7 = SingleTournamentRound.objects.get(roundnum=7, tournament=tournament) -# -# round1matches = round1.matches.all().order_by('id') -# round2matches = round2.matches.all().order_by('id') -# round3matches = round3.matches.all().order_by('id') -# round4matches = round4.matches.all().order_by('id') -# round5matches = round5.matches.all().order_by('id') -# round6matches = round6.matches.all().order_by('id') -# round7matches = round7.matches.all().order_by('id') -# -# return render(request, 'singletournaments/singletournament_matches.html', -# {'x': pk, 'tournament': tournament, -# 'round1matches': round1matches, 'round2matches': round2matches, -# 'round3matches': round3matches, 'round4matches': round4matches, -# 'round5matches': round5matches, 'round6matches': round6matches, -# 'round7matches': round7matches}) -# # get 7 rounds - - class SingleTournamentBracket(View): def get(self, request, **kwargs): From f2e1d409b43594d9b3ad5014d4a94613c44013ab Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 16:13:46 -0500 Subject: [PATCH 100/190] update changelog and version # --- changelog.md | 16 ++++++++++++++-- olly/base_settings.py | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index af54d7050..f5282376c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,12 @@ Project Olly Changelog -# 1.0.0 +# 1.0.0 - YES WE'VE MADE IT +Started in August of 2017, after much delays of life and school +we're happy to call this a release build of project-olly. Thank you to al our contributors +that have made this goal possible! The support in all shapes and forms don't go unnoticed! + +Please note the substantial and large +changes made in this update may affect compatibility with previous versions databases - Add SteamID64 and Discord profile fields - Add basic stats models (will be built upon further in later updates) - Added notification system for users @@ -11,9 +17,15 @@ Project Olly Changelog - Add free agents to Leagues - Allow staff to disable free agent registration for leagues within LeagueSettings - Implement point system for LeagueMatches -- Basis team checkin process ahead of matches +- Basic team checkin process ahead of matches - Multiple other staff panel league improvements - Front end league template improvements (standings, and more) +- Complete rework of tournament brackets - no more size restrictions, much more efficient brackets! +- Switched to jenkins for project builds, no more travisci +- Implement map random picking for tournaments and league matches +- Shift to single template tournament brackets +- General code cleanup and improvement +- DoubleElimination tournaments soon! # 0.9.0 - Implement leagues functionality diff --git a/olly/base_settings.py b/olly/base_settings.py index ac1a48216..6b760aa2e 100644 --- a/olly/base_settings.py +++ b/olly/base_settings.py @@ -158,4 +158,4 @@ LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/' -SITE_VERSION = "0.9.1" +SITE_VERSION = "1.0.0" From 1c0733fef47738c79bce00e33f70a0950b4a5325 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:05:06 -0500 Subject: [PATCH 101/190] prevent small chance of picking the same team twice --- singletournaments/models.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/singletournaments/models.py b/singletournaments/models.py index dd3d2d9e2..05a69fbac 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -136,16 +136,20 @@ def generate_bracket(self): teams = len(self.teams.all()) myteams = self.teams.all() round1 = SingleTournamentRound(tournament=self) + round1.save() if teams % 2 == 0: # no byes required - get 2 teams and make a match while len(myteams) != 0: temp1 = myteams.all().order_by("?").first() + myteams.remove(temp1) + myteams.save() temp2 = myteams.all().order_by("?").first() tempmatch = Match(awayteam=temp1, hometeam=temp2, maps=self.map_pool, game=self.game, platform=self.platform) tempmatch.save() - myteams.remove(temp1) myteams.remove(temp2) + myteams.save() round1.matches.add(tempmatch) + round1.save() else: # take the first team and give them a bye @@ -161,12 +165,15 @@ def generate_bracket(self): return while len(myteams) != 0: temp1 = myteams.all().order_by("?").first() + myteams.remove(temp1) + myteams.save() temp2 = myteams.all().order_by("?").first() tempmatch = Match(awayteam=temp1, hometeam=temp2, maps=self.map_pool, game=self.game, platform=self.platform) tempmatch.save() - myteams.remove(temp1) myteams.remove(temp2) + myteams.save() round1.matches.add(tempmatch) + round1.save() round1.save() From f0c81a8e524ec017cf70f1fa24c653579bc2b26e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:05:31 -0500 Subject: [PATCH 102/190] tweak maps field on matches --- matches/migrations/0003_auto_20201209_1705.py | 28 +++++++++++++++++++ matches/models.py | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 matches/migrations/0003_auto_20201209_1705.py diff --git a/matches/migrations/0003_auto_20201209_1705.py b/matches/migrations/0003_auto_20201209_1705.py new file mode 100644 index 000000000..207029f5d --- /dev/null +++ b/matches/migrations/0003_auto_20201209_1705.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0002_auto_20201209_1508'), + ] + + operations = [ + migrations.RemoveField( + model_name='match', + name='map', + ), + migrations.AddField( + model_name='match', + name='map_pool', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mappoolchoice', to='matches.MapPoolChoice'), + ), + migrations.AlterField( + model_name='match', + name='maps', + field=models.ManyToManyField(to='matches.MapChoice'), + ), + ] diff --git a/matches/models.py b/matches/models.py index a5e7f8a20..435db63c1 100644 --- a/matches/models.py +++ b/matches/models.py @@ -142,8 +142,8 @@ def __str__(self): class Match(models.Model): type = models.CharField(blank=True, null=True, max_length=20) matchnum = models.SmallIntegerField(default=0) - maps = models.ManyToManyField(MapPoolChoice) - map = models.ForeignKey(MapChoice, related_name='match_map', on_delete=models.SET_NULL, null=True) + map_pool = models.ForeignKey(MapPoolChoice, related_name='mappoolchoice', on_delete=models.SET_NULL, null=True) + maps = models.ManyToManyField(MapChoice) game = models.ForeignKey(GameChoice, related_name='GameChoice', on_delete=models.PROTECT, null=True) # default to ps4 for now bc why not platform = models.ForeignKey(PlatformChoice, related_name='PlatformChoice', on_delete=models.PROTECT, null=True) From a1cc07ca2af4d50a28a4b923e10b6a64b0d67763 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:26:06 -0500 Subject: [PATCH 103/190] staff view to randomly pick maps based on bestof --- staff/urls.py | 1 + staff/views/matches.py | 133 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/staff/urls.py b/staff/urls.py index a7da9e2f9..5fbdbb1b0 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -79,6 +79,7 @@ path('match//declare', login_required(views.MatchDeclareWinner.as_view()), name='match_declare_winner'), path('match//dispute', login_required(views.set_dispute_match), name='match_create_dispute'), path('match//delete', login_required(views.match_delete_winner), name='match_delete_winner'), + path('match//map-generate', login_required(views.pick_map), name='match_pickmap'), path('match//edit', login_required(views.match_edit), name='match_edit'), path('round//edit', login_required(views.edit_round), name='edit_round'), path('round//', login_required(views.round_detail), name='round_detail'), diff --git a/staff/views/matches.py b/staff/views/matches.py index fbfa19bc7..6d6498cb8 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -496,6 +496,139 @@ def create_map_pool_choice(request): return render(request, 'staff/matches/editmappool.html', {'form': form}) +def pick_map(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + match = Match.objects.get(pk=pk) + if match.bestof == 1: + # bo1 + # make sure the mappoolchoice is big enough + pool = match.map_pool + if len(pool.maps) >= 1: + map = pool.maps.all().order_by("?").first() + match.maps.add(map) + match.save() + else: + messages.error(request, "There are not enough maps in this map pool for Best of 1") + return redirect('staff:match_detail', pk=pk) + elif match.bestof == 2: + # bo2 + pool = match.map_pool + if len(pool.maps) >= 2: + map = pool.maps.all().order_by("?").first() + match.maps.add(map) + match.save() + pool.remove(map) + pool.save() + map2 = pool.maps.all().order_by("?").first() + match.maps.add(map2) + match.save() + else: + messages.error(request, "There are not enough maps in this map pool for Best of 2") + return redirect('staff:match_detail', pk=pk) + + elif match.bestof == 3: + # bo3 + # make sure the mappoolchoice is big enough + pool = match.map_pool + if len(pool.maps) >= 3: + map = pool.maps.all().order_by("?").first() + pool.remove(map) + pool.save() + match.maps.add(map) + match.save() + map2 = pool.maps.all().order_by("?").first() + pool.remove(map2) + pool.save() + match.maps.add(map2) + match.save() + map3 = pool.maps.all().order_by("?").first() + pool.remove(map3) + pool.save() + match.maps.add(map3) + match.save() + match.maps.add(map3) + match.save() + else: + messages.error(request, "There are not enough maps in this map pool for Best of 3") + return redirect('staff:match_detail', pk=pk) + + elif match.bestof == 4: + # bo4 + # make sure the mappoolchoice is big enough + pool = match.map_pool + if len(pool.maps) >= 4: + map = pool.maps.all().order_by("?").first() + pool.remove(map) + pool.save() + match.maps.add(map) + match.save() + map2 = pool.maps.all().order_by("?").first() + pool.remove(map2) + pool.save() + match.maps.add(map2) + match.save() + map3 = pool.maps.all().order_by("?").first() + pool.remove(map3) + pool.save() + match.maps.add(map3) + match.save() + map4 = pool.maps.all().order_by("?").first() + pool.remove(map4) + pool.save() + match.maps.add(map4) + match.save() + + else: + messages.error(request, "There are not enough maps in this map pool for Best of 4") + return redirect('staff:match_detail', pk=pk) + + + elif match.bestof == 5: + # bo5 + # make sure the mappoolchoice is big enough + pool = match.map_pool + if len(pool.maps) >= 5: + map = pool.maps.all().order_by("?").first() + pool.remove(map) + pool.save() + map2 = pool.maps.all().order_by("?").first() + pool.remove(map2) + pool.save() + map3 = pool.maps.all().order_by("?").first() + pool.remove(map3) + pool.save() + map4 = pool.maps.all().order_by("?").first() + pool.remove(map4) + pool.save() + map5 = pool.maps.all().order_by("?").first() + pool.remove(map5) + pool.save() + match.maps.add(map) + match.maps.add(map2) + match.maps.add(map3) + match.maps.add(map4) + match.maps.add(map5) + match.save() + else: + messages.error(request, "There are not enough maps in this map pool for Best of 5") + return redirect('staff:match_detail', pk=pk) + else: + messages.error(request, 'Unknown BestOf selected for this match') + return redirect('staff:match_detail', pk=pk) + + """ + (1, 'Best of 1'), + (2, 'Best of 2'), + (3, 'Best of 3'), + (4, 'Best of 4'), + (5, 'Best of 5'), + """ + + def match_checkins(request, pk): user = UserProfile.objects.get(user__username=request.user.username) From 364853c1614b1566a767969d41a5488a469cc1b3 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:26:24 -0500 Subject: [PATCH 104/190] staff match templates to redirect staff to generate all maps, show maps --- .../staff/matches/match_detail.html | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/project-templates/staff/matches/match_detail.html b/project-templates/staff/matches/match_detail.html index b97559ec8..43aca7e00 100644 --- a/project-templates/staff/matches/match_detail.html +++ b/project-templates/staff/matches/match_detail.html @@ -51,12 +51,25 @@
{% elif match.teamformat == 3 %} - {% elif tournament.teamformat == 4 %} + {% elif match.teamformat == 4 %} - {% elif tournament.teamformat == 5 %} + {% elif match.teamformat == 5 %} {% endif %} + + + + + + {% for map in match.maps.all %} + + {% endfor %} + + + + + {% if not match.bye_2 %} From 8ec203110740f9c886580fc5770c3200619c23e8 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:26:40 -0500 Subject: [PATCH 105/190] modify bestof in match.settings --- matches/settings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matches/settings.py b/matches/settings.py index 54ba3adca..f562aed87 100644 --- a/matches/settings.py +++ b/matches/settings.py @@ -10,9 +10,9 @@ )) MAPFORMAT_CHOICES = getattr(settings, 'MATCHES_MAPFORMAT_CHOICES', ( - (0, 'Best of 1'), - (1, 'Best of 3'), - (2, 'Best of 5'), - (3, 'Best of 7'), - (4, 'Best of 9'), + (1, 'Best of 1'), + (2, 'Best of 2'), + (3, 'Best of 3'), + (4, 'Best of 4'), + (5, 'Best of 5'), )) From f7435380a58ab284426b182897db68e61b433d83 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:26:57 -0500 Subject: [PATCH 106/190] change default bestof per new options --- matches/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matches/models.py b/matches/models.py index 435db63c1..e0cdba24e 100644 --- a/matches/models.py +++ b/matches/models.py @@ -165,7 +165,7 @@ class Match(models.Model): winner = models.ForeignKey(Team, related_name='champions', on_delete=models.SET_NULL, null=True) loser = models.ForeignKey(Team, related_name='loser', on_delete=models.SET_NULL, null=True) # set the default map format to best of 1 - bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=0) + bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=1) # by default set it to be a 2v2. teamformat = models.SmallIntegerField(choices=TEAMFORMAT_CHOICES, default=1) From 38184ed4d78f7af726b5f8aabd5cc2278cc3fb6f Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:29:46 -0500 Subject: [PATCH 107/190] migrations per new default values --- leagues/migrations/0003_auto_20201209_1721.py | 18 ++++++++++++++++++ leagues/migrations/0004_auto_20201209_1729.py | 18 ++++++++++++++++++ leagues/models.py | 2 +- matches/migrations/0004_auto_20201209_1721.py | 18 ++++++++++++++++++ .../migrations/0002_auto_20201209_1721.py | 18 ++++++++++++++++++ .../migrations/0003_auto_20201209_1729.py | 18 ++++++++++++++++++ singletournaments/models.py | 2 +- wagers/migrations/0002_auto_20201209_1721.py | 18 ++++++++++++++++++ wagers/migrations/0003_auto_20201209_1729.py | 18 ++++++++++++++++++ wagers/models.py | 14 +++++++------- 10 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 leagues/migrations/0003_auto_20201209_1721.py create mode 100644 leagues/migrations/0004_auto_20201209_1729.py create mode 100644 matches/migrations/0004_auto_20201209_1721.py create mode 100644 singletournaments/migrations/0002_auto_20201209_1721.py create mode 100644 singletournaments/migrations/0003_auto_20201209_1729.py create mode 100644 wagers/migrations/0002_auto_20201209_1721.py create mode 100644 wagers/migrations/0003_auto_20201209_1729.py diff --git a/leagues/migrations/0003_auto_20201209_1721.py b/leagues/migrations/0003_auto_20201209_1721.py new file mode 100644 index 000000000..0d6851855 --- /dev/null +++ b/leagues/migrations/0003_auto_20201209_1721.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('leagues', '0002_auto_20201209_1508'), + ] + + operations = [ + migrations.AlterField( + model_name='league', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=0), + ), + ] diff --git a/leagues/migrations/0004_auto_20201209_1729.py b/leagues/migrations/0004_auto_20201209_1729.py new file mode 100644 index 000000000..c11d79bb6 --- /dev/null +++ b/leagues/migrations/0004_auto_20201209_1729.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('leagues', '0003_auto_20201209_1721'), + ] + + operations = [ + migrations.AlterField( + model_name='league', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=1), + ), + ] diff --git a/leagues/models.py b/leagues/models.py index 7a9b904e6..1afec0703 100644 --- a/leagues/models.py +++ b/leagues/models.py @@ -107,7 +107,7 @@ class League(models.Model): teamformat = models.SmallIntegerField(choices=TEAMFORMAT_CHOICES, default=1) # by default its a best of 1. Not sure if we need this here. Finals might be best of 3, etc in # the future possibly. TBD. For now this will work though. - bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=0) + bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=1) # manually open registration even if outside registration window allow_register = models.BooleanField(default=False) # when does registration open, and when does it close? specified when created in staff panel diff --git a/matches/migrations/0004_auto_20201209_1721.py b/matches/migrations/0004_auto_20201209_1721.py new file mode 100644 index 000000000..cd96c04f8 --- /dev/null +++ b/matches/migrations/0004_auto_20201209_1721.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0003_auto_20201209_1705'), + ] + + operations = [ + migrations.AlterField( + model_name='match', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=1), + ), + ] diff --git a/singletournaments/migrations/0002_auto_20201209_1721.py b/singletournaments/migrations/0002_auto_20201209_1721.py new file mode 100644 index 000000000..59c6fa2f6 --- /dev/null +++ b/singletournaments/migrations/0002_auto_20201209_1721.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('singletournaments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='singleeliminationtournament', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=0), + ), + ] diff --git a/singletournaments/migrations/0003_auto_20201209_1729.py b/singletournaments/migrations/0003_auto_20201209_1729.py new file mode 100644 index 000000000..8e0d18b6e --- /dev/null +++ b/singletournaments/migrations/0003_auto_20201209_1729.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('singletournaments', '0002_auto_20201209_1721'), + ] + + operations = [ + migrations.AlterField( + model_name='singleeliminationtournament', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=1), + ), + ] diff --git a/singletournaments/models.py b/singletournaments/models.py index 05a69fbac..33fcab981 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -34,7 +34,7 @@ class SingleEliminationTournament(models.Model): teamformat = models.SmallIntegerField(choices=TEAMFORMAT_CHOICES, default=1) # by default its a best of 1. Not sure if we need this here. Finals might be best of 3, etc in # the future possibly. TBD. For now this will work though. - bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=0) + bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=1) # by default the tournament is not active. an admin has to activate it in order for it to be public active = models.BooleanField(default=False) diff --git a/wagers/migrations/0002_auto_20201209_1721.py b/wagers/migrations/0002_auto_20201209_1721.py new file mode 100644 index 000000000..1889f517f --- /dev/null +++ b/wagers/migrations/0002_auto_20201209_1721.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagers', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='wagerrequest', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=0), + ), + ] diff --git a/wagers/migrations/0003_auto_20201209_1729.py b/wagers/migrations/0003_auto_20201209_1729.py new file mode 100644 index 000000000..9a273e642 --- /dev/null +++ b/wagers/migrations/0003_auto_20201209_1729.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-09 22:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagers', '0002_auto_20201209_1721'), + ] + + operations = [ + migrations.AlterField( + model_name='wagerrequest', + name='bestof', + field=models.SmallIntegerField(choices=[(1, 'Best of 1'), (2, 'Best of 2'), (3, 'Best of 3'), (4, 'Best of 4'), (5, 'Best of 5')], default=1), + ), + ] diff --git a/wagers/models.py b/wagers/models.py index d31124ad2..5110f7c62 100644 --- a/wagers/models.py +++ b/wagers/models.py @@ -28,7 +28,7 @@ class WagerRequest(models.Model): credits = models.PositiveIntegerField(default=5) game = models.ForeignKey(GameChoice, related_name='game_choices', on_delete=models.PROTECT) platform = models.ForeignKey(PlatformChoice, related_name='platform_choices', on_delete=models.PROTECT) - bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=0) + bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=1) teamformat = models.SmallIntegerField(choices=TEAMFORMAT_CHOICES, default=1) info = models.TextField(default="No additional info given") creator = models.ForeignKey(User, related_name='creator_user', on_delete=models.CASCADE, null=True) @@ -50,13 +50,13 @@ def get_min_team_size(self): return 6 def get_best_of(self): - if self.bestof == 0: - return 1 if self.bestof == 1: - return 3 + return 1 if self.bestof == 2: - return 5 + return 2 if self.bestof == 3: - return 7 + return 3 if self.bestof == 4: - return 9 + return 4 + if self.bestof == 5: + return 5 From ceddc8708d177cf87867dbb2e60068c693b710df Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:41:19 -0500 Subject: [PATCH 108/190] logic to show map info on tournament detail template --- .../matches/tournament_matches_detail.html | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/project-templates/matches/tournament_matches_detail.html b/project-templates/matches/tournament_matches_detail.html index 3fab550b4..f37b67eed 100644 --- a/project-templates/matches/tournament_matches_detail.html +++ b/project-templates/matches/tournament_matches_detail.html @@ -14,7 +14,7 @@
{% if not match.disable_userreports %} -

Submit your match report here.

+

Submit your match report here.

{% endif %}
From 325ace59bd4cad83e6b163d19f45663c5c03d538 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 17:52:04 -0500 Subject: [PATCH 109/190] fix team errors from previous versions --- teams/forms.py | 2 +- teams/views.py | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/teams/forms.py b/teams/forms.py index 62f9c8c9f..01f4158ba 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -43,7 +43,7 @@ def __init__(self, request, *args, **kwargs): self.fields['captain'].widget.attrs.update({'name': 'captain', 'class': 'form-control', 'style': 'width:30%'}) self.username = request.user - invites = TeamInvite.objects.filter(hasPerms=True, user=request.user, accepted=True) + #invites = TeamInvite.objects.filter(hasPerms=True, user=request.user, accepted=True) profile = UserProfile.objects.get(user=request.user) tlist = profile.captain_teams.all() | profile.founder_teams.all() # tlist = profile.captain_teams.all() + profile.founder_teams.all() diff --git a/teams/views.py b/teams/views.py index aaa42c014..7c8044ac7 100644 --- a/teams/views.py +++ b/teams/views.py @@ -156,7 +156,7 @@ class MyTeamDetailView(DetailView): def get(self, request, pk): team = get_object_or_404(Team, id=pk) players = team.players.all() - captains = team.captains.all() + captains = team.captain.all() matches_ = Match.objects.filter(awayteam_id=team.id) matches__ = Match.objects.filter(hometeam_id=team.id) matches = matches_ | matches__ @@ -216,17 +216,6 @@ def post(self, request): Team.save() profile.founder_teams.add(Team) profile.save() - invite = TeamInvite() - invite.expire = timezone.now() - invite.user = self.request.user - invite.captain = 'founder' - invite.hasPerms = True - invite.accepted = True - invite.inviter = self.request.user - invite.inviter_id = self.request.user.id - invite.team_id = Team.id - invite.save() - messages.success(self.request, 'Your Team has been created successfully') return redirect('teams:detail', pk=Team.pk) From d52d6accc03520b9cc1980155e57157ce96d04fb Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 18:13:54 -0500 Subject: [PATCH 110/190] allow modifying a matches bestof --- project-templates/staff/matches/match_edit.html | 5 +++++ staff/forms.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/project-templates/staff/matches/match_edit.html b/project-templates/staff/matches/match_edit.html index 13b5aa404..5b5d0e76e 100644 --- a/project-templates/staff/matches/match_edit.html +++ b/project-templates/staff/matches/match_edit.html @@ -38,6 +38,11 @@ {{ form.info }}
+ + + {{ form.bestof }} +
+ {{ form.disable_userreport }}
diff --git a/staff/forms.py b/staff/forms.py index 250017585..a6a695dd4 100644 --- a/staff/forms.py +++ b/staff/forms.py @@ -233,7 +233,7 @@ class Meta: class EditMatchForm(forms.ModelForm): class Meta: model = Match - fields = ('info', 'disable_userreport') + fields = ('info', 'disable_userreport', 'bestof') class GameChoiceForm(forms.ModelForm): From 636ae83218d2e877e647a041b46b80bf00ee8487 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 18:14:11 -0500 Subject: [PATCH 111/190] fix match filtering in staff match list --- staff/views/matches.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index 6d6498cb8..dc6715db6 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -19,7 +19,7 @@ def matches_index(request): return render(request, 'staff/permissiondenied.html') else: # matches_list = Match.objects.all().order_by('-id') - tmatches = Match.objects.filter(type__isnull=True) + tmatches = Match.objects.filter(type="singletournament") wmatches = Match.objects.filter(type='w') lmatches = Match.objects.filter(type="leagues") return render(request, 'staff/matches/matches.html', {'tmatches': tmatches, 'wmatches': wmatches, 'lmatches': lmatches}) From ec83c16dc2ee70cd928203078d01d44bc62422ca Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 18:14:35 -0500 Subject: [PATCH 112/190] tweak and fix map choice logic --- staff/views/matches.py | 90 ++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index dc6715db6..28c03255e 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -503,29 +503,35 @@ def pick_map(request, pk): return render(request, 'staff/permissiondenied.html') else: match = Match.objects.get(pk=pk) + match.maps.clear() + match.save() if match.bestof == 1: # bo1 # make sure the mappoolchoice is big enough - pool = match.map_pool - if len(pool.maps) >= 1: - map = pool.maps.all().order_by("?").first() + pool = match.map_pool.maps + if pool.count() >= 1: + map = pool.all().order_by("?").first() match.maps.add(map) match.save() + messages.success(request, "Maps updated!") + return redirect('staff:match_detail', pk=pk) else: messages.error(request, "There are not enough maps in this map pool for Best of 1") return redirect('staff:match_detail', pk=pk) elif match.bestof == 2: # bo2 - pool = match.map_pool - if len(pool.maps) >= 2: - map = pool.maps.all().order_by("?").first() + pool = match.map_pool.maps + if pool.count() >= 2: + map = pool.all().order_by("?").first() match.maps.add(map) match.save() pool.remove(map) - pool.save() - map2 = pool.maps.all().order_by("?").first() + #pool.save() + map2 = pool.all().order_by("?").first() match.maps.add(map2) match.save() + messages.success(request, "Maps updated!") + return redirect('staff:match_detail', pk=pk) else: messages.error(request, "There are not enough maps in this map pool for Best of 2") return redirect('staff:match_detail', pk=pk) @@ -533,25 +539,27 @@ def pick_map(request, pk): elif match.bestof == 3: # bo3 # make sure the mappoolchoice is big enough - pool = match.map_pool - if len(pool.maps) >= 3: - map = pool.maps.all().order_by("?").first() + pool = match.map_pool.maps + if pool.count() >= 3: + map = pool.all().order_by("?").first() pool.remove(map) - pool.save() + #pool.save() match.maps.add(map) match.save() - map2 = pool.maps.all().order_by("?").first() + map2 = pool.all().order_by("?").first() pool.remove(map2) - pool.save() + #pool.save() match.maps.add(map2) match.save() - map3 = pool.maps.all().order_by("?").first() + map3 = pool.all().order_by("?").first() pool.remove(map3) - pool.save() + #pool.save() match.maps.add(map3) match.save() match.maps.add(map3) match.save() + messages.success(request, "Maps updated!") + return redirect('staff:match_detail', pk=pk) else: messages.error(request, "There are not enough maps in this map pool for Best of 3") return redirect('staff:match_detail', pk=pk) @@ -559,29 +567,30 @@ def pick_map(request, pk): elif match.bestof == 4: # bo4 # make sure the mappoolchoice is big enough - pool = match.map_pool - if len(pool.maps) >= 4: - map = pool.maps.all().order_by("?").first() + pool = match.map_pool.maps + if pool.count() >= 4: + map = pool.all().order_by("?").first() pool.remove(map) - pool.save() + #pool.save() match.maps.add(map) match.save() - map2 = pool.maps.all().order_by("?").first() + map2 = pool.all().order_by("?").first() pool.remove(map2) - pool.save() + #pool.save() match.maps.add(map2) match.save() - map3 = pool.maps.all().order_by("?").first() + map3 = pool.all().order_by("?").first() pool.remove(map3) - pool.save() + #pool.save() match.maps.add(map3) match.save() - map4 = pool.maps.all().order_by("?").first() + map4 = pool.all().order_by("?").first() pool.remove(map4) - pool.save() + #pool.save() match.maps.add(map4) match.save() - + messages.success(request, "Maps updated!") + return redirect('staff:match_detail', pk=pk) else: messages.error(request, "There are not enough maps in this map pool for Best of 4") return redirect('staff:match_detail', pk=pk) @@ -590,32 +599,35 @@ def pick_map(request, pk): elif match.bestof == 5: # bo5 # make sure the mappoolchoice is big enough - pool = match.map_pool - if len(pool.maps) >= 5: - map = pool.maps.all().order_by("?").first() + pool = match.map_pool.maps + if pool.count() >= 5: + map = pool.all().order_by("?").first() pool.remove(map) - pool.save() - map2 = pool.maps.all().order_by("?").first() + #pool.save() + map2 = pool.all().order_by("?").first() pool.remove(map2) - pool.save() - map3 = pool.maps.all().order_by("?").first() + #pool.save() + map3 = pool.all().order_by("?").first() pool.remove(map3) - pool.save() - map4 = pool.maps.all().order_by("?").first() + #pool.save() + map4 = pool.all().order_by("?").first() pool.remove(map4) - pool.save() - map5 = pool.maps.all().order_by("?").first() + #pool.save() + map5 = pool.all().order_by("?").first() pool.remove(map5) - pool.save() + #pool.save() match.maps.add(map) match.maps.add(map2) match.maps.add(map3) match.maps.add(map4) match.maps.add(map5) match.save() + messages.success(request, "Maps updated!") + return redirect('staff:match_detail', pk=pk) else: messages.error(request, "There are not enough maps in this map pool for Best of 5") return redirect('staff:match_detail', pk=pk) + else: messages.error(request, 'Unknown BestOf selected for this match') return redirect('staff:match_detail', pk=pk) From 8fb2a6489c5afd6f5c1ba6d44ec6b50b4e9b4440 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 9 Dec 2020 18:14:44 -0500 Subject: [PATCH 113/190] remove old tournament round link --- project-templates/staff/matches/match_detail.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/project-templates/staff/matches/match_detail.html b/project-templates/staff/matches/match_detail.html index 43aca7e00..b82220ecf 100644 --- a/project-templates/staff/matches/match_detail.html +++ b/project-templates/staff/matches/match_detail.html @@ -16,8 +16,6 @@ -

Back to Round

IDTeam NameDate Created
{{ team.id }}{{ team.name }}{{ team.created }}
3v34v45v56v6
Best of {{ match.bestof }}
Maps: {{ map.name }}
Choosen mapsRandomly Repick maps (in given Best of format)
Home team
From c323e50d4d1ee6c93e48d73e9f83dd607fce4b10 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 10 Dec 2020 17:14:46 -0500 Subject: [PATCH 114/190] rework bracket advance logic --- singletournaments/models.py | 4 +- staff/views/singletournaments.py | 82 ++++++++------------------------ 2 files changed, 24 insertions(+), 62 deletions(-) diff --git a/singletournaments/models.py b/singletournaments/models.py index 33fcab981..19e1a7f4f 100644 --- a/singletournaments/models.py +++ b/singletournaments/models.py @@ -135,7 +135,9 @@ def set_inactive(self, **kwargs): def generate_bracket(self): teams = len(self.teams.all()) myteams = self.teams.all() - round1 = SingleTournamentRound(tournament=self) + self.current_round = 1 + self.save() + round1 = SingleTournamentRound(tournament=self, roundnum=1) round1.save() if teams % 2 == 0: # no byes required - get 2 teams and make a match diff --git a/staff/views/singletournaments.py b/staff/views/singletournaments.py index 4ea94dced..dced9df96 100644 --- a/staff/views/singletournaments.py +++ b/staff/views/singletournaments.py @@ -239,69 +239,29 @@ def advance(request, pk): return render(request, 'staff/permissiondenied.html') else: tournament = SingleEliminationTournament.objects.get(pk=pk) - currentround = SingleTournamentRound.objects.get(tournament=pk, roundnum=tournament.current_round) - try: - nextround = SingleTournamentRound.objects.get(tournament=tournament, roundnum=tournament.current_round + 1) - except: - messages.warning(request, "All rounds are complete") - tournament.active = False - tournament.save() + currentround = SingleTournamentRound.objects.get(tournament=tournament, roundnum=tournament.current_round) + nextround = SingleTournamentRound(tournament=tournament, roundnum=tournament.current_round+1) + winners = Team.objects.none() + tournament.current_round = tournament.current_round+1 + tournament.save() + for x in currentround.matches.all(): + winners.append(x.winner) + if len(winners) % 2 != 0: + messages.error(request, 'Invalid round') return redirect('staff:tournamentlist') - matches = currentround.matches.all() - for i in matches: - if i.winner is None: - if mikes_super_function(currentround.id) is False: - messages.error(request, "Some matches in the current round do not have a winner set") - return redirect('staff:tournamentlist') - else: - mike = mikes_super_function(currentround.id) - - # if i.completed is False: - # messages.error(request, 'There is a match that is not yet marked as completed in the current round') - # return redirect('staff:tournamentlist') - - winners = [] + while len(winners) != 0: + temp1 = winners.order_by("?").first() + winners.remove(temp1) + winners.save() + temp2 = winners.order_by("?").first() + tempmatch = Match(awayteam=temp1, hometeam=temp2, maps=tournament.map_pool, game=tournament.game, + platform=tournament.platform) + tempmatch.save() + winners.remove(temp2) + winners.save() + nextround.matches.add(tempmatch) + nextround.save() - for i in matches: - - try: - if i.winner is None: - winners.append('BYE TEAM') - - else: - winners.append(i.winner) - team = Team.objects.get(id=i.winner_id) - team.num_matchwin += 1 - team.save() - team1 = Team.objects.get(id=i.loser_id) - team1.num_matchloss += 1 - team1.save() - except: - pass - - # check to make sure mike + - - i = 0 - while i < len(winners): - if winners[i] == 'BYE TEAM': - # disable user reports, its a bye match - newmatch = Match(game=tournament.game, platform=tournament.platform, hometeam=winners[i + 1], - disable_userreport=True, sport=tournament.sport) - elif winners[i + 1] == 'BYE TEAM': - # disable user reports, its a bye match - newmatch = Match(game=tournament.game, platform=tournament.platform, sport=tournament.sport, - awayteam=winners[i], disable_userreport=True) - else: - newmatch = Match(game=tournament.game, platform=tournament.platform, - awayteam=winners[i], hometeam=winners[i + 1], - # disable user match reports based on the field in the tournament - disable_userreport=tournament.disable_userreport, sport=tournament.sport) - newmatch.save() - nextround.matches.add(newmatch) - i += 2 - - tournament.current_round = tournament.current_round + 1 - tournament.save() messages.success(request, "Advanced to next round") return redirect('staff:tournamentlist') From bbc475c70fd96e1874b7c846f82358eb0c558e80 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 10 Dec 2020 17:19:59 -0500 Subject: [PATCH 115/190] better error checking and handling on tournament advance --- staff/views/singletournaments.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/staff/views/singletournaments.py b/staff/views/singletournaments.py index dced9df96..3aca71063 100644 --- a/staff/views/singletournaments.py +++ b/staff/views/singletournaments.py @@ -245,9 +245,18 @@ def advance(request, pk): tournament.current_round = tournament.current_round+1 tournament.save() for x in currentround.matches.all(): + if x.winner is None: + messages.error(request, 'Error: There is not a winner for a match in the previous round, cannot advance') + return redirect(request, 'staff:tournamentlist') + if not x.completed: + messages.error(request, 'Error: There is a match that is not marked as completed yet') + return redirect(request, 'staff:tournamentlist') winners.append(x.winner) if len(winners) % 2 != 0: - messages.error(request, 'Invalid round') + messages.error(request, 'Error: Invalid round') + return redirect('staff:tournamentlist') + if len(winners) == 1: + messages.error(request, 'Warning: The tournament is over, cannot advance further') return redirect('staff:tournamentlist') while len(winners) != 0: temp1 = winners.order_by("?").first() From aa6c0a2ba9e393c9b876acfd3d38d9811a0d8546 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Mon, 14 Dec 2020 18:46:34 -0500 Subject: [PATCH 116/190] Allow founder_teams, captain_teams, and player_teams to be blank --- .../migrations/0002_auto_20201214_1845.py | 28 +++++++++++++++++++ profiles/models.py | 6 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 profiles/migrations/0002_auto_20201214_1845.py diff --git a/profiles/migrations/0002_auto_20201214_1845.py b/profiles/migrations/0002_auto_20201214_1845.py new file mode 100644 index 000000000..6448b46d2 --- /dev/null +++ b/profiles/migrations/0002_auto_20201214_1845.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.7 on 2020-12-14 23:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='captain_teams', + field=models.ManyToManyField(blank=True, related_name='profile_captain_teams', to='teams.Team'), + ), + migrations.AlterField( + model_name='userprofile', + name='founder_teams', + field=models.ManyToManyField(blank=True, related_name='profile_founder_teams', to='teams.Team'), + ), + migrations.AlterField( + model_name='userprofile', + name='player_teams', + field=models.ManyToManyField(blank=True, related_name='profile_player_teams', to='teams.Team'), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 12196962b..0c671041d 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -94,11 +94,11 @@ def __str__(self): email_enabled = models.BooleanField(default=True) # teams the user founded - founder_teams = models.ManyToManyField('teams.Team', related_name='profile_founder_teams') + founder_teams = models.ManyToManyField('teams.Team', related_name='profile_founder_teams', blank=True) # teams the user is a captain of - captain_teams = models.ManyToManyField('teams.Team', related_name='profile_captain_teams') + captain_teams = models.ManyToManyField('teams.Team', related_name='profile_captain_teams', blank=True) # teams the user is a player on - player_teams = models.ManyToManyField('teams.Team', related_name='profile_player_teams') + player_teams = models.ManyToManyField('teams.Team', related_name='profile_player_teams', blank=True) def calculate_rank(self): self.rank = int(UserProfile.objects.filter(xp__gt=self.xp).count()) + 1 From 480428e96bef7bb9a06478df4b595382ce13942c Mon Sep 17 00:00:00 2001 From: Steven Young Date: Mon, 14 Dec 2020 20:49:31 -0500 Subject: [PATCH 117/190] Fix images on singletournament detail --- project-static/images/gamepad-solid.svg | 1 + project-static/images/trophy-solid.svg | 1 + .../singletournaments/singletournament_detail.html | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 project-static/images/gamepad-solid.svg create mode 100644 project-static/images/trophy-solid.svg diff --git a/project-static/images/gamepad-solid.svg b/project-static/images/gamepad-solid.svg new file mode 100644 index 000000000..216acf533 --- /dev/null +++ b/project-static/images/gamepad-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/project-static/images/trophy-solid.svg b/project-static/images/trophy-solid.svg new file mode 100644 index 000000000..543ed2271 --- /dev/null +++ b/project-static/images/trophy-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/project-templates/singletournaments/singletournament_detail.html b/project-templates/singletournaments/singletournament_detail.html index 091399b21..c8a891427 100644 --- a/project-templates/singletournaments/singletournament_detail.html +++ b/project-templates/singletournaments/singletournament_detail.html @@ -31,7 +31,7 @@ {% if ESPORTS_MODE %} - {% if tournament.game.image %}{{ MEDIA_URL }}{{ tournament.game.image }}{% else %}{% static 'images/bf1cover.jpg' %}{% endif %}" + {% if tournament.game.image %}{{ MEDIA_URL }}{{ tournament.game.image }}{% else %}{% static 'images/gamepad-solid.svg' %}{% endif %}" /> {% else %} {{ tournament.sport.name }} From 9ecaf62fba28db6cb8931d331f64fbfa045bc177 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Mon, 14 Dec 2020 20:51:33 -0500 Subject: [PATCH 118/190] Move singletournament rules from a separate page to detail --- .../singletournament_detail.html | 14 ++++++++------ .../singletournament_list.html | 2 +- .../singletournament_rules.html | 18 ------------------ singletournaments/urls.py | 1 - singletournaments/views.py | 10 ---------- 5 files changed, 9 insertions(+), 36 deletions(-) delete mode 100644 project-templates/singletournaments/singletournament_rules.html diff --git a/project-templates/singletournaments/singletournament_detail.html b/project-templates/singletournaments/singletournament_detail.html index c8a891427..0291c6583 100644 --- a/project-templates/singletournaments/singletournament_detail.html +++ b/project-templates/singletournaments/singletournament_detail.html @@ -52,11 +52,11 @@

{{ tournament.platform.name }} | {{ tournament.game.name }}

+ {% csrf_token %} + {{ form.as_table }} + + + +
+ +
+ + {% endblock %} \ No newline at end of file From 1fff82c788881af6624cc8cdad6cfc1aaa17ca1c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 15 Dec 2020 13:52:47 -0500 Subject: [PATCH 123/190] fix manyrelatedmanager issues in tournament views --- singletournaments/views.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/singletournaments/views.py b/singletournaments/views.py index aa6c8ff52..c3d044249 100644 --- a/singletournaments/views.py +++ b/singletournaments/views.py @@ -49,7 +49,7 @@ class SingleTournamentJoin(View): def get(self, request, pk): # teaminvites = TeamInvite.objects.filter(user_id=request.user.id, hasPerms=True) profile = UserProfile.objects.get(user=request.user) - teams = profile.founder_teams + profile.captain_teams + teams = profile.founder_teams.all() | profile.captain_teams.all() tournament = get_object_or_404(SingleEliminationTournament, id=pk) if teams.count() != 0: form = SingleEliminationTournamentJoinGet(request) @@ -63,7 +63,7 @@ def post(self, request, pk): form = SingleEliminationTournamentJoinPost(request.POST) profile = UserProfile.objects.get(user=request.user) team = Team.objects.get(id=int(form.data['teams'])) - if team in profile.captain_teams or team in profile.founder_teams: + if team in profile.captain_teams.all() or team in profile.founder_teams.all(): # good to go pass else: @@ -147,24 +147,24 @@ def post(self, request, pk): return redirect('singletournaments:list') # loop through every user on the team trying to join - see if they're a player/founder/captain # on any other team thats already registered - for user in team.players: - for otherteam in tournament.teams: - for player in otherteam.players: + for user in team.players.all(): + for otherteam in tournament.teams.all(): + for player in otherteam.players.all(): if user == player: messages.error(request, "There is overlap between users in teams in the tournament") return redirect('singletournaments:list') - for captain in otherteam.captain: + for captain in otherteam.captain.all(): if user == captain: messages.error(request, "There is overlap between users in teams in the tournament") return redirect('singletournaments:list') - if user == otherteam.captain: + if user == otherteam.captain.all(): messages.error(request, "There is overlap between users in teams in the tournament") return redirect('singletournaments:list') tournament.teams.add(team) - for user in team.players: + for user in team.players.all(): deduct_credits(user, tournament.req_credits) - for captain in team.captain: + for captain in team.captain.all(): deduct_credits(captain, tournament.req_credits) deduct_credits(team.founder, tournament.req_credits) tournament.save() @@ -198,7 +198,7 @@ def post(self, request, pk): team = SingleTournamentTeam.objects.get(team_id=user_team.id, tournament=tournament) team.delete() tournament.teams.remove(user_team) - team_users = user_team.players + user_team.founder + user_team.captain + team_users = user_team.players.all() | user_team.founder | user_team.captain.all() for user in team_users: give_credits(user=user, num=tournament.req_credits) messages.success(request, "Gave %s credits to %s users" % (tournament.req_credits, len(team_users))) From 0b58219b2886a46920a4659325df217b87a853da Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 15 Dec 2020 13:53:08 -0500 Subject: [PATCH 124/190] fix manyrelatedobject issues with match list --- matches/views.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/matches/views.py b/matches/views.py index 76483f022..09b3f23fa 100644 --- a/matches/views.py +++ b/matches/views.py @@ -14,7 +14,6 @@ import datetime - class MapPoolDetail(DetailView): def get(self, request, **kwargs): pk = self.kwargs['pk'] @@ -26,8 +25,10 @@ def get(self, request, **kwargs): class MatchList(View): def get(self, request): - teams = Team.objects.filter( - Q(captains__exact=request.user) | Q(founder=request.user) | Q(players__exact=request.user)) + # teams = Team.objects.filter( + # Q(captains__exact=request.user) | Q(founder=request.user) | Q(players__exact=request.user)) + profile = UserProfile.objects.get(user=request.user) + teams = profile.player_teams.all() | profile.captain_teams.all() | profile.founder_teams.all() matches_away = Match.objects.filter(awayteam__in=teams) matches_home = Match.objects.filter(hometeam__in=teams) matches = matches_away | matches_home @@ -278,7 +279,7 @@ def team_checkin(request, pk, teamid): elif request.method == 'POST': # lets make it and get the data # TODO: find a way to get the team instance in the form - form = TeamCheckInForm(request.POST,) + form = TeamCheckInForm(request.POST, ) if form.is_valid(): temp = MatchCheckIn() temp.match = match @@ -287,7 +288,7 @@ def team_checkin(request, pk, teamid): # TODO: verify posted data from form of field 'players' temp.players = form.cleaned_data['players'] temp.save() - messages.success(request, 'Your team has been checked in, checkin #'+temp.pk) + messages.success(request, 'Your team has been checked in, checkin #' + temp.pk) return redirect('matches:detail', pk=match.pk) else: messages.error(request, "Form error, this should not occur") @@ -295,4 +296,4 @@ def team_checkin(request, pk, teamid): def create_checkin(request, pk): - pass \ No newline at end of file + pass From e6384ec6118da3be67c549a4f513333988462e1c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 15 Dec 2020 13:56:01 -0500 Subject: [PATCH 125/190] fix model name issues --- matches/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matches/views.py b/matches/views.py index 09b3f23fa..eaa6d29fd 100644 --- a/matches/views.py +++ b/matches/views.py @@ -86,12 +86,12 @@ def post(self, request, pk): one_perms = False two_perms = False - if self.request.user in team1.captains or self.request.user == team1.founder: + if self.request.user in team1.captain.all() or self.request.user == team1.founder: one_perms = True - elif self.request.user in team2.captains or self.request.user == team2.founder: + elif self.request.user in team2.captain.all() or self.request.user == team2.founder: two_perms = True else: - messages.error(request, message="You aren't a part of the teams in this match") + messages.error(request, message="You don't have permission to report for any team in this match") if match.type == 'w': return redirect('wagers:list') return redirect('matches:detail', pk=pk) From 3a39890c74670c36a26a03f4666e1756ffdb8080 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 15 Dec 2020 14:25:46 -0500 Subject: [PATCH 126/190] pep8 allow blank=True for more fields --- matches/migrations/0005_auto_20201215_1425.py | 34 +++++++++++++++++++ matches/models.py | 15 ++++---- teams/migrations/0002_auto_20201215_1425.py | 34 +++++++++++++++++++ teams/models.py | 8 ++--- 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 matches/migrations/0005_auto_20201215_1425.py create mode 100644 teams/migrations/0002_auto_20201215_1425.py diff --git a/matches/migrations/0005_auto_20201215_1425.py b/matches/migrations/0005_auto_20201215_1425.py new file mode 100644 index 000000000..6e5bde17a --- /dev/null +++ b/matches/migrations/0005_auto_20201215_1425.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.15 on 2020-12-15 19:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0004_auto_20201209_1721'), + ] + + operations = [ + migrations.AlterField( + model_name='match', + name='loser', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loser', to='teams.Team'), + ), + migrations.AlterField( + model_name='match', + name='maps', + field=models.ManyToManyField(blank=True, to='matches.MapChoice'), + ), + migrations.AlterField( + model_name='match', + name='sport', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='SportChoice', to='matches.SportChoice'), + ), + migrations.AlterField( + model_name='match', + name='winner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='champions', to='teams.Team'), + ), + ] diff --git a/matches/models.py b/matches/models.py index e0cdba24e..9ae1784a4 100644 --- a/matches/models.py +++ b/matches/models.py @@ -84,6 +84,9 @@ class MatchStats(models.Model): class SportChoice(models.Model): name = models.CharField(default='unknown sports', null=False, max_length=255) + def __str__(self): + return "" + self.name + class GameChoice(models.Model): name = models.CharField(default='unknown', null=False, max_length=255) @@ -130,7 +133,7 @@ class MapPoolChoice(models.Model): created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) - #def add_map(self, mappk): + # def add_map(self, mappk): # newmap = MapChoice.objects.get(id=mappk) # newmap.map_num = self.maps.count() + 1 # self.maps.add(newmap) @@ -143,12 +146,12 @@ class Match(models.Model): type = models.CharField(blank=True, null=True, max_length=20) matchnum = models.SmallIntegerField(default=0) map_pool = models.ForeignKey(MapPoolChoice, related_name='mappoolchoice', on_delete=models.SET_NULL, null=True) - maps = models.ManyToManyField(MapChoice) + maps = models.ManyToManyField(MapChoice, blank=True) game = models.ForeignKey(GameChoice, related_name='GameChoice', on_delete=models.PROTECT, null=True) # default to ps4 for now bc why not platform = models.ForeignKey(PlatformChoice, related_name='PlatformChoice', on_delete=models.PROTECT, null=True) # support for traditional sports - sport = models.ForeignKey(SportChoice, related_name='SportChoice', on_delete=models.PROTECT, null=True) + sport = models.ForeignKey(SportChoice, related_name='SportChoice', on_delete=models.PROTECT, null=True, blank=True) # assign the match to a tournament with a FK # tournament = models.ForeignKey(SingleEliminationTournament, related_name='tournament', on_delete=models.CASCADE) # fk fields for the 2 teams that are competiting, @@ -162,8 +165,8 @@ class Match(models.Model): # simple bool field to see if the entire match is completed completed = models.BooleanField(default=False) # field to declare the winner - winner = models.ForeignKey(Team, related_name='champions', on_delete=models.SET_NULL, null=True) - loser = models.ForeignKey(Team, related_name='loser', on_delete=models.SET_NULL, null=True) + winner = models.ForeignKey(Team, related_name='champions', on_delete=models.SET_NULL, null=True, blank=True) + loser = models.ForeignKey(Team, related_name='loser', on_delete=models.SET_NULL, null=True, blank=True) # set the default map format to best of 1 bestof = models.SmallIntegerField(choices=MAPFORMAT_CHOICES, default=1) # by default set it to be a 2v2. @@ -177,7 +180,7 @@ class Match(models.Model): null=True, blank=True) # TODO: implement datetime field for matches - #datetime = models.DateTimeField(null=True) + # datetime = models.DateTimeField(null=True) info = models.TextField(default="Match Info: ") diff --git a/teams/migrations/0002_auto_20201215_1425.py b/teams/migrations/0002_auto_20201215_1425.py new file mode 100644 index 000000000..4fabfca0c --- /dev/null +++ b/teams/migrations/0002_auto_20201215_1425.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.15 on 2020-12-15 19:25 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='captain', + field=models.ManyToManyField(blank=True, related_name='teamcaptain', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='team', + name='matches', + field=models.ManyToManyField(blank=True, related_name='team_matches', to='matches.Match'), + ), + migrations.AlterField( + model_name='team', + name='players', + field=models.ManyToManyField(blank=True, related_name='teamplayers', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='team', + name='team_stat', + field=models.ManyToManyField(blank=True, related_name='match_team_stat', to='matches.TeamMatchStats'), + ), + ] diff --git a/teams/models.py b/teams/models.py index 9cb953486..ccd2842f5 100644 --- a/teams/models.py +++ b/teams/models.py @@ -21,9 +21,9 @@ class Team(models.Model): # whoever filled out the form to create the team, limited to only one founder = models.ForeignKey(User, related_name='founder', on_delete=models.SET_NULL, null=True) # basically founder permissions, but to other people that didn't create the actual team - captain = models.ManyToManyField(User, related_name='teamcaptain') + captain = models.ManyToManyField(User, related_name='teamcaptain', blank=True) # the people of the actual team, now a many to many, not a forkey - players = models.ManyToManyField(User, related_name='teamplayers') + players = models.ManyToManyField(User, related_name='teamplayers', blank=True) # when they created the team created = models.DateTimeField(auto_now_add=True) # when they last updated anything in the team @@ -44,8 +44,8 @@ class Team(models.Model): country = CountryField(blank=True) image = models.ImageField(upload_to='team_images', blank=True) - matches = models.ManyToManyField('matches.Match', related_name='team_matches') - team_stat = models.ManyToManyField('matches.TeamMatchStats', related_name='match_team_stat') + matches = models.ManyToManyField('matches.Match', related_name='team_matches', blank=True) + team_stat = models.ManyToManyField('matches.TeamMatchStats', related_name='match_team_stat', blank=True) class Meta: verbose_name = 'Team' From 8ab2019283e2941a6a46fa53224f34a51c4773ed Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Tue, 15 Dec 2020 17:21:11 -0500 Subject: [PATCH 127/190] match checkin process improvements and fixes --- matches/forms.py | 5 ++-- matches/views.py | 27 ++++++++++++-------- project-templates/matches/match_checkin.html | 4 +-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/matches/forms.py b/matches/forms.py index a00ad0b32..1c6b46a98 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -42,8 +42,9 @@ class Meta: class TeamCheckInForm(forms.Form): #TODO: define queryset for players team checkin form players = forms.ModelMultipleChoiceField(queryset=None) - def __init__(self, team): - mylist = team.players + team.founder + team.captain + forfeit = forms.Che + def __init__(self, request, team): + mylist = team.players.all() | team.founder | team.captain.all() super().__init__() self.fields['players'].widget.attrs.update({'name': 'players', 'class': 'form-control'}) self.fields['players'].queryset = mylist diff --git a/matches/views.py b/matches/views.py index eaa6d29fd..705778e3c 100644 --- a/matches/views.py +++ b/matches/views.py @@ -250,14 +250,20 @@ def form_valid(self, form, **kwargs): def match_checkin(request, pk): match = Match.objects.get(pk=pk) - user = UserProfile.objects.get(user__username=request.user.username) + profile = UserProfile.objects.get(user__username=request.user.username) + user = request.user away = match.awayteam home = match.hometeam - if user not in away.captain or user not in home.captain or user is not away.founder or user is not home.founder: - messages.error(request, "You don't have permission to checkin for this match") - return redirect('match:detail', pk=pk) - else: + if (user in away.captain.all() or user in home.captain.all()) or (user is away.founder and user is home.founder): return render(request, 'matches/match_checkin.html', {'match': match}) + else: + messages.error(request, "222You don't have permission to checkin for this match") + return redirect('matches:detail', pk=pk) + + """elif : + messages.error(request, "You don't have permission to checkin for this match") + return redirect('matches:detail', pk=pk) + """ def team_checkin(request, pk, teamid): @@ -266,20 +272,21 @@ def team_checkin(request, pk, teamid): # get the match from the pk in the url match = Match.objects.get(pk=pk) # get logged in user - user = UserProfile.objects.get(user__username=request.user.username) - if user is not team.founder or user not in team.captain: + profile = UserProfile.objects.get(user__username=request.user.username) + user = request.user + if user is not team.founder and user not in team.captain.all(): # they don't have perms - gtfo - messages.error(request, "You do not have permissions to checkin this team") + messages.error(request, "FFFFFYou do not have permissions to checkin this team") return redirect('matches:detail', pk=pk) if request.method == 'GET': # send the form, render - form = TeamCheckInForm(request.GET) + form = TeamCheckInForm(request=request.GET, team=team) return render(request, 'matches/team_checkin.html', {'form': form}) pass elif request.method == 'POST': # lets make it and get the data # TODO: find a way to get the team instance in the form - form = TeamCheckInForm(request.POST, ) + form = TeamCheckInForm(request=request.POST, team=team) if form.is_valid(): temp = MatchCheckIn() temp.match = match diff --git a/project-templates/matches/match_checkin.html b/project-templates/matches/match_checkin.html index db9c7d70a..2c6fc0045 100644 --- a/project-templates/matches/match_checkin.html +++ b/project-templates/matches/match_checkin.html @@ -32,6 +32,6 @@

Please note only captains and team founder's can checkin for matches

To proceed with match checkin process, please click the corresponding button below.
You will be redirected to a new page where you will select the players that will be playing in the match

- - + + {% endblock %} \ No newline at end of file From 3d207505fcaba975a388c3a82cf5bbc90f6f82dd Mon Sep 17 00:00:00 2001 From: Steven Young Date: Wed, 16 Dec 2020 14:54:39 -0500 Subject: [PATCH 128/190] Set branch name var and attempt adding author to commit --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7e8514ae4..3edfbacef 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,11 +24,11 @@ pipeline { always { script { env.msg = "**Status:** " + currentBuild.currentResult.toLowerCase() + "\n" - env.msg += "**Branch:** ${env.GIT_LOCAL_BRANCH}\n" + env.msg += "**Branch:** ${env.BRANCH_NAME}\n" env.msg += "**Changes:** \n" if (!currentBuild.changeSets.isEmpty()) { currentBuild.changeSets.first().getLogs().each { - env.msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + "*\n" + env.msg += "- `" + it.getCommitId().substring(0, 8) + "` *" + it.getComment().substring(0, it.getComment().length()-1) + it.getAuthor() + "*\n" } } else { env.msg += "no changes for this run\n" @@ -36,7 +36,7 @@ pipeline { if (env.msg.length() > 1024) env.msg.take(env.msg.length() - 1024) } withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.GIT_LOCAL_BRANCH} #${env.BUILD_NUMBER}", webhookURL: env.WEBHOOK_URL + discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} Build #${env.BUILD_NUMBER} ${currentBuild.CurrentResult}", webhookURL: env.WEBHOOK_URL } } cleanup { From b96e098ac4b6f41ca528bc4e8e23521844b65611 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Wed, 16 Dec 2020 15:45:07 -0500 Subject: [PATCH 129/190] This is case sensitive --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3edfbacef..8aa743c48 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,7 +36,7 @@ pipeline { if (env.msg.length() > 1024) env.msg.take(env.msg.length() - 1024) } withCredentials([string(credentialsId: 'Webook_URL', variable: 'WEBHOOK_URL')]) { - discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} Build #${env.BUILD_NUMBER} ${currentBuild.CurrentResult}", webhookURL: env.WEBHOOK_URL + discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} Build #${env.BUILD_NUMBER} ${currentBuild.currentResult}", webhookURL: env.WEBHOOK_URL } } cleanup { From 32075b0bd92bd24cf0a401095634bfc800397a8c Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 19:48:24 -0500 Subject: [PATCH 130/190] syntax fix --- matches/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matches/forms.py b/matches/forms.py index 1c6b46a98..f5fce53c2 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -42,7 +42,7 @@ class Meta: class TeamCheckInForm(forms.Form): #TODO: define queryset for players team checkin form players = forms.ModelMultipleChoiceField(queryset=None) - forfeit = forms.Che + #forfeit = forms.Che def __init__(self, request, team): mylist = team.players.all() | team.founder | team.captain.all() super().__init__() From 4324e818bd0b5f644c69c0d983024ecd72bb281d Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 19:56:36 -0500 Subject: [PATCH 131/190] fix queryset for tournament join form --- singletournaments/forms.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/singletournaments/forms.py b/singletournaments/forms.py index 87ed4f0a9..73ad0b57d 100644 --- a/singletournaments/forms.py +++ b/singletournaments/forms.py @@ -3,6 +3,7 @@ from singletournaments.models import SingleEliminationTournament from teams.models import Team, TeamInvite from matches.models import PlatformChoice, GameChoice +from profiles.models import UserProfile class SingleEliminationTournamentJoinGet(forms.ModelForm): @@ -16,11 +17,13 @@ class Meta: def __init__(self, request, *args, **kwargs): self.username = request.user - invites = TeamInvite.objects.filter(hasPerms=True, user_id=self.username.id) - team = Team.objects.filter(id__in=invites.values_list('team', flat=True)) + #invites = TeamInvite.objects.filter(hasPerms=True, user_id=self.username.id) + profile = UserProfile.objects.get(user=request.user) + teams = profile.captain_teams | profile.founder_teams + #team = Team.objects.filter(id__in=invites.values_list('team', flat=True)) super().__init__(*args, **kwargs) self.fields['teams'].widget.attrs.update({'name': 'teams', 'class': 'form-control'}) - self.fields['teams'].queryset = team + self.fields['teams'].queryset = teams class SingleEliminationTournamentJoinPost(forms.ModelForm): From e8457a325314509fbc116fe9494fb2ad8013742a Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:40:28 -0500 Subject: [PATCH 132/190] allow staff to force add players to teams --- .../staff/teams/force_addplayer.html | 32 +++++++++++++++++ staff/views/teams.py | 34 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 project-templates/staff/teams/force_addplayer.html diff --git a/project-templates/staff/teams/force_addplayer.html b/project-templates/staff/teams/force_addplayer.html new file mode 100644 index 000000000..9f765ebae --- /dev/null +++ b/project-templates/staff/teams/force_addplayer.html @@ -0,0 +1,32 @@ +{% extends 'staff/staffbase.html' %} +{% load static %} + + +{% block title %} + Add Player to {{ team.name }} +{% endblock %} + +{% block body %} + + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + +
+ +

Force add a user to a team as a player - Enter username below.

+

Form will error out if the user already exists as a player on the team

+ {% csrf_token %} + {{ form.as_table }} + + + +
+ +
+
+{% endblock %} diff --git a/staff/views/teams.py b/staff/views/teams.py index b11ef2d04..919ff221d 100644 --- a/staff/views/teams.py +++ b/staff/views/teams.py @@ -24,12 +24,44 @@ def teams_detail(request, pk): else: team = Team.objects.get(id=pk) players = team.players.all() - captains = team.captains.all() + captains = team.captain.all() return render(request, 'staff/teams/team_detail.html', {'team': team, 'players': players, 'captains': captains, 'pk': pk}) +def force_addplayer(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + team = Team.objects.get(id=pk) + if request.method == "GET": + form = TeamForceAddUser() + return render(request, 'staff/teams/force_addplayer.html', {'form': form, 'team': team}) + else: + form = TeamForceAddUser(request.POST) + if form.is_valid(): + muser = form.cleaned_data['user'] + user = User.objects.get(username=muser) + try: + temp = UserProfile.objects.get(user=user) + except: + messages.error(request, 'Unable to find user') + return redirect('staff:team_detail', pk=team.id) + if (temp.user in team.players.all()) or (temp.user in team.captain.all()) or (temp.user == team.founder): + messages.error(request, "This user already exists on the team") + return redirect('staff:team_detail', pk=team.id) + team.players.add(temp.user) + team.save() + messages.success(request, "Successfully added user to team - as role:player") + return redirect('staff:team_detail', pk=team.id) + else: + messages.error(request, "Unknown Form Error") + return redirect('staff:team_detail', pk=team.id) + + def create_team(request): user = UserProfile.objects.get(user__username=request.user.username) allowed = ['superadmin', 'admin'] From df465e6ba48f81b1c453b7fd15ee99abc6f5d156 Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:40:48 -0500 Subject: [PATCH 133/190] fix staff player remove - no more teaminvites --- staff/views/teams.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/staff/views/teams.py b/staff/views/teams.py index 919ff221d..40c8682ba 100644 --- a/staff/views/teams.py +++ b/staff/views/teams.py @@ -96,7 +96,6 @@ def delete_team(request, pk): return redirect('staff:teamindex') -#TODO remove TeamInvite object def remove_user(request, pk): user = UserProfile.objects.get(user__username=request.user.username) allowed = ['superadmin', 'admin'] @@ -105,17 +104,28 @@ def remove_user(request, pk): else: if request.method == 'POST': form = RemovePlayerFormPost(request.POST) - invite = TeamInvite.objects.get(id=form.data['remove']) - messages.success(request, 'Removed user %s from team' % invite) - invite.delete() - invites = TeamInvite.objects.filter(team_id=pk) - if not invites.exists(): - team = Team.objects.get(id=pk) - team.delete() - messages.success(request, 'Deleted team due to the last user being removed') - return redirect('staff:teamindex') + team = Team.objects.get(pk=pk) + muser = form.data['remove'] + user = User.objects.get(user=muser) + if user in team.players.all(): + try: + team.players.remove(form.data['remove']) + team.save() + except: + messages.error(request, "Failed to remove player from team") + return redirect('staff:team_detail', pk=team.id) + elif user in team.captain.all(): + try: + team.captain.remove(form.data['remove']) + team.save() + except: + messages.error(request, "Failed to remove user as captain from team") + return redirect('staff:team_detail', pk=team.id) else: - return redirect('staff:team_detail', pk=pk) + messages.error(request, "Unable to verify user is on the team as player/captain") + return redirect('staff:team_detail', pk=team.id) + messages.success(request, 'Removed user %s from team' % user) + return redirect('staff:team_detail', pk=pk) else: form = RemovePlayerForm(request, pk) return render(request, 'staff/teams/team_remove_player.html', {'form': form, 'pk': pk}) From 0b601b1a00a6e2d8236e6c02e75696f12784d331 Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:41:03 -0500 Subject: [PATCH 134/190] route for staff:addplayer --- staff/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/staff/urls.py b/staff/urls.py index 5fbdbb1b0..47a2580bc 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -130,6 +130,7 @@ path('teams/', login_required(views.teams_index), name='teamindex'), path('teams/create/', login_required(views.create_team), name='create_team'), path('teams//', login_required(views.teams_detail), name='team_detail'), + path('teams//addplayer/', login_required(views.force_addplayer), name='add_player'), path('teams//remove/', login_required(views.remove_user), name='remove_user'), path('teams/getrank/', login_required(views.getteamrank), name='getteamrank'), path('teams//delete/', login_required(views.delete_team), name='delete_team'), From 894390ecbeb1cc229f36753d0de3e249b106a2d2 Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:41:23 -0500 Subject: [PATCH 135/190] staff:teamdetail pep8 and addplayer button --- project-templates/staff/teams/team_detail.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project-templates/staff/teams/team_detail.html b/project-templates/staff/teams/team_detail.html index 73815a083..c6a8c02d2 100644 --- a/project-templates/staff/teams/team_detail.html +++ b/project-templates/staff/teams/team_detail.html @@ -33,7 +33,7 @@ Captains {% for captain in captains %} - {{ captain.name }} + {{ captain.name }} {% endfor %} @@ -80,5 +80,6 @@
Delete Team + Add Player {% endblock %} From f32e0e6ea1f17a8fafaeb35bf08f9aad7ff4045b Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:41:41 -0500 Subject: [PATCH 136/190] fix remove player form, and add teamforceadduser form --- staff/forms.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/staff/forms.py b/staff/forms.py index a6a695dd4..a468ca8e9 100644 --- a/staff/forms.py +++ b/staff/forms.py @@ -182,27 +182,19 @@ class Meta: } -class RemovePlayerForm(forms.ModelForm): +class RemovePlayerForm(forms.Form): remove = forms.ModelChoiceField(queryset=None) - class Meta: - model = TeamInvite - fields = () - def __init__(self, request, pk, *args, **kwargs): team = Team.objects.get(id=pk) - players = TeamInvite.objects.filter(team=team, accepted=True) + players = team.players.all() | team.captain.all() super().__init__(*args, **kwargs) self.fields['remove'].queryset = players -class RemovePlayerFormPost(forms.ModelForm): +class RemovePlayerFormPost(forms.Form): remove = forms.ModelChoiceField(queryset=None) - class Meta: - model = TeamInvite - fields = () - class ModifyUserForm(forms.ModelForm): class Meta: @@ -318,6 +310,10 @@ class Meta: } +class TeamForceAddUser(forms.Form): + user = forms.CharField(required=True, max_length=50) + + class CreateLeagueSettingsForm(forms.ModelForm): class Meta: model = LeagueSettings From b177afd9b78704dfbb0b391279f945ecebdc7d6d Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:46:16 -0500 Subject: [PATCH 137/190] pass current utc.datetime to staff templates for simplicity --- project-templates/staff/leagues/league_create.html | 4 ++-- project-templates/staff/leagues/league_detail.html | 6 +++--- project-templates/staff/leagues/league_edit.html | 4 ++-- staff/views/leagues.py | 10 +++++++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/project-templates/staff/leagues/league_create.html b/project-templates/staff/leagues/league_create.html index f3aca5c2d..761342a36 100644 --- a/project-templates/staff/leagues/league_create.html +++ b/project-templates/staff/leagues/league_create.html @@ -60,14 +60,14 @@

Return to league list

- + {{ form.open_register }}
- + {{ form.close_register }}
diff --git a/project-templates/staff/leagues/league_detail.html b/project-templates/staff/leagues/league_detail.html index 10f1dcd85..cbe4ef2e9 100644 --- a/project-templates/staff/leagues/league_detail.html +++ b/project-templates/staff/leagues/league_detail.html @@ -34,12 +34,12 @@ {{ league.settings.name }} - Start Date/Time + Start Date/Time -- Current UTC Time {{ time }} {{ league.start }} - Registration Open - {{ league.open_register }} + Registration Open->Close -- Current UTC Time {{ time }} + {{ league.open_register }}->{{ league.close_register }} Required Credits diff --git a/project-templates/staff/leagues/league_edit.html b/project-templates/staff/leagues/league_edit.html index f9e60e676..b9cd0d9ca 100644 --- a/project-templates/staff/leagues/league_edit.html +++ b/project-templates/staff/leagues/league_edit.html @@ -60,14 +60,14 @@

Return to league list

- + {{ form.open_register }}
- + {{ form.close_register }}
diff --git a/staff/views/leagues.py b/staff/views/leagues.py index 47297f866..5ce569dd4 100644 --- a/staff/views/leagues.py +++ b/staff/views/leagues.py @@ -1,6 +1,7 @@ from django.contrib import messages from django.shortcuts import render, redirect from django.core.exceptions import ObjectDoesNotExist +import datetime #from django.views.generic import View #from matches.models import MatchReport, MatchDispute, Match, MapChoice, MapPoolChoice @@ -15,7 +16,8 @@ def create_league(request): else: if request.method == 'GET': form = CreateLeagueForm - return render(request, 'staff/leagues/league_create.html', {'form': form}) + time = datetime.datetime.utcnow() + return render(request, 'staff/leagues/league_create.html', {'form': form, 'time': time}) else: # the form is posting, lets start validating form = CreateLeagueForm(request.POST, request.FILES) @@ -47,7 +49,8 @@ def detail_league(request, pk): else: league = League.objects.get(pk=pk) divisions = league.divisions.all() - return render(request, 'staff/leagues/league_detail.html', {'league': league, 'divisions': divisions}) + time = datetime.datetime.utcnow() + return render(request, 'staff/leagues/league_detail.html', {'league': league, 'divisions': divisions, 'time': time}) # list all the teams in the league and the divisions @@ -92,7 +95,8 @@ def edit_league(request, pk): else: league = League.objects.get(pk=pk) form = CreateLeagueForm(instance=league) - return render(request, 'staff/leagues/league_edit.html', {'form': form, 'pk':pk, 'league':league}) + time = datetime.datetime.utcnow() + return render(request, 'staff/leagues/league_edit.html', {'form': form, 'pk':pk, 'league':league, 'time': time}) def list_league_settings(request): From 07aef3400d6fd0f7ac031515b03045668f56d0cb Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:48:26 -0500 Subject: [PATCH 138/190] pass utc time staff league list --- project-templates/staff/leagues/league_list.html | 5 +++-- staff/views/leagues.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/project-templates/staff/leagues/league_list.html b/project-templates/staff/leagues/league_list.html index 429a323ca..4d124b2a2 100644 --- a/project-templates/staff/leagues/league_list.html +++ b/project-templates/staff/leagues/league_list.html @@ -9,6 +9,7 @@ {% block body %}

Create a ruleset

+

Current UTC Date Time = {{ time }}

@@ -40,8 +41,8 @@

Create a ruleset

{{ league.game.name }} - - + + {% if league.active %} diff --git a/staff/views/leagues.py b/staff/views/leagues.py index 5ce569dd4..b388e6671 100644 --- a/staff/views/leagues.py +++ b/staff/views/leagues.py @@ -38,7 +38,8 @@ def list_league(request): return render(request, 'staff/permissiondenied.html') else: leagues = League.objects.all() - return render(request, 'staff/leagues/league_list.html', {'leagues': leagues}) + time = datetime.datetime.utcnow() + return render(request, 'staff/leagues/league_list.html', {'leagues': leagues, 'time': time}) def detail_league(request, pk): From 41632e873c91c681bba2aa1641c97396f79900c5 Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:50:53 -0500 Subject: [PATCH 139/190] pass datetime to tournament staff templates --- .../singletournaments/singletournament_create.html | 4 ++-- .../singletournaments/singletournament_detail.html | 1 + .../singletournaments/singletournament_edit.html | 1 + .../singletournaments/singletournament_list.html | 4 +--- staff/views/singletournaments.py | 11 +++++++---- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/project-templates/staff/singletournaments/singletournament_create.html b/project-templates/staff/singletournaments/singletournament_create.html index 88c6e7263..5d33c0724 100644 --- a/project-templates/staff/singletournaments/singletournament_create.html +++ b/project-templates/staff/singletournaments/singletournament_create.html @@ -50,14 +50,14 @@

Return to tournament list

- + {{ form.open_register }}
- + {{ form.close_register }}
diff --git a/project-templates/staff/singletournaments/singletournament_detail.html b/project-templates/staff/singletournaments/singletournament_detail.html index f375d6e69..68d7ecea5 100644 --- a/project-templates/staff/singletournaments/singletournament_detail.html +++ b/project-templates/staff/singletournaments/singletournament_detail.html @@ -10,6 +10,7 @@

Back to Tournament list

+

-- Current UTC Time {{ time }}

{{ league.start }}{{ league.open_register }}{{ league.req_credits }}{{ league.open_register }}-UTC Time{{ league.req_credits }}-UTC TimeActive
diff --git a/project-templates/staff/singletournaments/singletournament_edit.html b/project-templates/staff/singletournaments/singletournament_edit.html index 7554881e5..03da26d1a 100644 --- a/project-templates/staff/singletournaments/singletournament_edit.html +++ b/project-templates/staff/singletournaments/singletournament_edit.html @@ -29,6 +29,7 @@ {% endfor %} {% endif %}

Return to tournament list

+

Current UTC Time {{ time }}

{% csrf_token %} diff --git a/project-templates/staff/singletournaments/singletournament_list.html b/project-templates/staff/singletournaments/singletournament_list.html index 5a1cae65b..0ec31991a 100644 --- a/project-templates/staff/singletournaments/singletournament_list.html +++ b/project-templates/staff/singletournaments/singletournament_list.html @@ -8,9 +8,7 @@ {% block body %}
-

Always lower the size of a tournament to the lowest possible amount - BEFORE launching -
- This will prevent the system from making a lot of extra byes.

+

Current UTC Time {{ time }}

Create a ruleset

diff --git a/staff/views/singletournaments.py b/staff/views/singletournaments.py index 3aca71063..dc6a8eeb1 100644 --- a/staff/views/singletournaments.py +++ b/staff/views/singletournaments.py @@ -1,7 +1,7 @@ from django.contrib import messages from django.shortcuts import render, redirect from django.views.generic import View - +import datetime from staff.forms import * from wagers.models import * from .tools import calculaterank @@ -40,7 +40,8 @@ def tournaments(request): return render(request, 'staff/permissiondenied.html') else: tournament_list = SingleEliminationTournament.objects.all().order_by('-id') - return render(request, 'staff/singletournaments/singletournament_list.html', {'tournament_list': tournament_list}) + time = datetime.datetime.utcnow() + return render(request, 'staff/singletournaments/singletournament_list.html', {'tournament_list': tournament_list, 'time': time}) def tournament_detail(request, pk): @@ -104,9 +105,10 @@ def edit_tournament(request, pk): return render(request, 'staff/singletournaments/singletournament_edit.html', {'form': form}) else: tournamentobj = SingleEliminationTournament.objects.get(pk=pk) + time = datetime.datetime.utcnow() if not tournamentobj.bracket_generated: form = EditTournamentForm(instance=tournamentobj) - return render(request, 'staff/singletournaments/singletournament_edit.html', {'form': form, 'pk': pk}) + return render(request, 'staff/singletournaments/singletournament_edit.html', {'form': form, 'pk': pk, 'time': time}) else: messages.error(request, 'You cannot edit a launched tournament') return redirect('staff:tournamentlist') @@ -120,7 +122,8 @@ def create_tournament(request): else: if request.method == 'GET': form = CreateTournamentForm() - return render(request, 'staff/singletournaments/singletournament_create.html', {'form': form}) + time = datetime.datetime.utcnow() + return render(request, 'staff/singletournaments/singletournament_create.html', {'form': form, 'time': time}) else: form = CreateTournamentForm(request.POST, request.FILES) if form.is_valid(): From 9a2fdbe18ae971674c5a08d1fe600903a26b811d Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 20:56:14 -0500 Subject: [PATCH 140/190] fix front end user remove --- project-templates/teams/team_detail.html | 3 +-- teams/forms.py | 2 +- teams/views.py | 14 ++++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/project-templates/teams/team_detail.html b/project-templates/teams/team_detail.html index 03e4fc084..94a57495b 100644 --- a/project-templates/teams/team_detail.html +++ b/project-templates/teams/team_detail.html @@ -80,8 +80,7 @@ - + {% endfor %} {% if players.count == 0 %} diff --git a/teams/forms.py b/teams/forms.py index 01f4158ba..6f0fef16e 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -106,7 +106,7 @@ class RemoveUserForm(forms.Form): def __init__(self, request, pk, *args, **kwargs): team = Team.objects.get(id=pk) - players = team.players.all() + players = team.players.all() | team.captain.all() super().__init__(*args, **kwargs) self.fields['remove'].queryset = players self.fields['remove'].widget.attrs.update( diff --git a/teams/views.py b/teams/views.py index 7c8044ac7..94140a382 100644 --- a/teams/views.py +++ b/teams/views.py @@ -335,7 +335,7 @@ class RemoveUserView(View): def get(self, request, pk): team = Team.objects.get(id=pk) - if request.user == team.founder or request.user in team.captains: + if request.user == team.founder or request.user in team.captain.all(): form = RemoveUserForm(request, pk) return render(request, 'teams/team_remove_user.html', {'form': form, 'pk': pk}) else: @@ -344,17 +344,19 @@ def get(self, request, pk): def post(self, request, pk): team = Team.objects.get(id=pk) - if request.user == team.founder or request.user in team.captains: + if request.user == team.founder or request.user in team.captain.all(): form = RemovePlayerFormPost(request.POST) # invite = TeamInvite.objects.get(id=form.data['remove']) - player = UserProfile.objects.get(form.data['remove']) - if player == team.founder: + user = form.data['remove'] + player = UserProfile.objects.get(user=user) + if player.user == team.founder: messages.error(request, "You cannot remove the Team founder from the team") - return redirect('teams:list') + return redirect('teams:detail', pk=pk) else: - team.players.remove(player) + team.players.remove(player.user) team.save() messages.success(request, 'Removed user %s from team' % player) + return redirect('team:detail', pk=pk) else: messages.error(request, "Only the team's founder or a captain can remove users") From fc442336484df809bf0b539fc3eb12707c32ac5c Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 21:11:04 -0500 Subject: [PATCH 141/190] fix team edit view --- teams/views.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/teams/views.py b/teams/views.py index 94140a382..80abfb06d 100644 --- a/teams/views.py +++ b/teams/views.py @@ -119,31 +119,28 @@ def MyTeamsListView(request): def edit_team_view(request, pk): + teamobj = get_object_or_404(Team, id=pk) + profile = UserProfile.objects.get(user=request.user) if request.method == 'POST': - teamobj = get_object_or_404(Team, id=pk) form = EditTeamProfileForm(request.POST, request.FILES, instance=teamobj) - if request.user not in teamobj.captains or request.user is not teamobj.founder: - messages.error(request, 'ERROR: You must be a captain or founder update team info') - return redirect('teams:detail', pk=teamobj.pk) - if form.is_valid(): - # teamobj.about_us = form.data['about_us'] - # teamobj.website = form.data['website'] - # teamobj.twitter = form.data['twitter'] - # teamobj.twitch = form.data['twitch'] - # teamobj.country = form.data['country'] - # teamobj.image = form.data['image'] - # teamobj.save() - - form.save() - messages.success(request, 'Team successfully updated') - return redirect(reverse('teams:detail', args=[pk])) + if profile.user in teamobj.captain.all() or profile.user == teamobj.founder: + if form.is_valid(): + form.save() + messages.success(request, 'Team successfully updated') + return redirect(reverse('teams:detail', args=[pk])) + else: + messages.error(request, 'An unknown error has occured') + return redirect(reverse('teams:detail', args=[pk])) else: - messages.error(request, 'An unknown error has occured') - return redirect(reverse('teams:detail', args=[pk])) + messages.error(request, 'ERROR: You must be a captain or founder to update team info') + return redirect('teams:detail', pk=teamobj.pk) else: - teamobj = Team.objects.get(id=pk) - form = EditTeamProfileForm(instance=teamobj) - return render(request, 'teams/team_edit.html', {'form': form}) + if profile.user in teamobj.captain.all() or profile.user == teamobj.founder: + form = EditTeamProfileForm(instance=teamobj) + return render(request, 'teams/team_edit.html', {'form': form}) + else: + messages.error(request, 'ERROR: You must be a captain or founder to update team info') + return redirect('teams:detail', pk=teamobj.pk) class MyTeamDetailView(DetailView): From eb28a036f1baec90ef36d96a8b9c36b7eb26b255 Mon Sep 17 00:00:00 2001 From: Mike M Date: Thu, 17 Dec 2020 21:11:25 -0500 Subject: [PATCH 142/190] show country flag as url instead of default image --- project-templates/teams/team_detail.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project-templates/teams/team_detail.html b/project-templates/teams/team_detail.html index 94a57495b..c25a70356 100644 --- a/project-templates/teams/team_detail.html +++ b/project-templates/teams/team_detail.html @@ -25,7 +25,11 @@ From 8015c225084f11fb05e9ab30a047323b5ede0f36 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 24 Dec 2020 12:32:23 -0500 Subject: [PATCH 143/190] Remove travis.yml --- .travis.yml | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dd7abf31f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,46 +0,0 @@ -language: python - -python: - - 3.7 - -services: - - postgresql - - docker - -addons: - hosts: - - project-olly-db - -install: - - docker build -t nfmstudios/project-olly:master . - -before_script: - - psql -c "CREATE DATABASE olly;" -U postgres - - psql -c "CREATE USER olly WITH PASSWORD 'secret_password';" -U postgres - - psql -c "GRANT ALL PRIVILEGES ON DATABASE olly to olly;" -U postgres - - psql -c "ALTER USER olly CREATEDB;" -U postgres - -script: - - docker run --network=host --env-file .env.example nfmstudios/project-olly:master python3 manage.py test - -after_success: - - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - - chmod +x send.sh - - ./send.sh success $WEBHOOK_URL -after_failure: - - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - - chmod +x send.sh - - ./send.sh failure $WEBHOOK_URL - -notifications: - email: - recipients: - - nfm.studios@gmail.com - on_success: never - on_failure: always - -deploy: - provider: script - script: echo "$DOCKER_PASSWORD" | docker login -u $DOCKER_USERNAME --password-stdin && docker push nfmstudios/project-olly:master - on: - branch: master \ No newline at end of file From 7d79b539156085f111c4f7432042f18645a12852 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 24 Dec 2020 13:53:32 -0500 Subject: [PATCH 144/190] Push master to docker hub if build succeeds --- Jenkinsfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 8aa743c48..f00bb26b5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,6 +39,15 @@ pipeline { discordSend description: "${env.msg}", link: env.BUILD_URL, result: currentBuild.currentResult, title: "Project Olly:${env.BRANCH_NAME} Build #${env.BUILD_NUMBER} ${currentBuild.currentResult}", webhookURL: env.WEBHOOK_URL } } + success { + script { + if (env.BRANCH_NAME == 'master') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'dockerhub', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { + sh'echo "$PASSWORD" | docker login -u $USERNAME --password-stdin && docker push nfmstudios/project-olly:master' + } + } + } + } cleanup { sh '''#!/bin/bash docker rm -f project-olly 2> /dev/null From 67e8d922c3debcab75524d1aaacb61308602e122 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 17:08:13 -0500 Subject: [PATCH 145/190] teamcheckin form get works, post having blank queryset issues and invalid --- matches/forms.py | 5 +++-- matches/views.py | 7 ++++--- project-templates/matches/team_checkin.html | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/matches/forms.py b/matches/forms.py index f5fce53c2..69eb41888 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -40,11 +40,12 @@ class Meta: class TeamCheckInForm(forms.Form): - #TODO: define queryset for players team checkin form + #TODO: allow forfeit before match starts + #TODO: allow request for sub player players = forms.ModelMultipleChoiceField(queryset=None) #forfeit = forms.Che def __init__(self, request, team): - mylist = team.players.all() | team.founder | team.captain.all() + mylist = team.players.all() | team.captain.all() # | team.founder super().__init__() self.fields['players'].widget.attrs.update({'name': 'players', 'class': 'form-control'}) self.fields['players'].queryset = mylist diff --git a/matches/views.py b/matches/views.py index 705778e3c..417f3a06f 100644 --- a/matches/views.py +++ b/matches/views.py @@ -280,9 +280,8 @@ def team_checkin(request, pk, teamid): return redirect('matches:detail', pk=pk) if request.method == 'GET': # send the form, render - form = TeamCheckInForm(request=request.GET, team=team) - return render(request, 'matches/team_checkin.html', {'form': form}) - pass + form = TeamCheckInForm(team=team, request=request.GET) + return render(request, 'matches/team_checkin.html', {'form': form, 'match': match, 'teamid': teamid}) elif request.method == 'POST': # lets make it and get the data # TODO: find a way to get the team instance in the form @@ -298,6 +297,8 @@ def team_checkin(request, pk, teamid): messages.success(request, 'Your team has been checked in, checkin #' + temp.pk) return redirect('matches:detail', pk=match.pk) else: + print('dammit') + print(form.cleaned_data) messages.error(request, "Form error, this should not occur") return redirect('matches:detail', pk=pk) diff --git a/project-templates/matches/team_checkin.html b/project-templates/matches/team_checkin.html index 5b02c2b6a..b22ca9198 100644 --- a/project-templates/matches/team_checkin.html +++ b/project-templates/matches/team_checkin.html @@ -8,10 +8,10 @@ {% block body %}

Please select the players from your team below that are playing in the match

- +
{{ profile.user }}{% if profile.xbl_verified and profile.psn_verified %}YES{% else %} - NO{% endif %}
{% if team.image != "" %}
{% else %} -
+ {% if team.country != "" %} +
+ {% else %} +

No image yet

+ {% endif %} {% endif %}
{% csrf_token %} - {{ form.as_table }} + {{ form.as_p }}
From be2152dd2f471e978bbf084cb24a305ee19e3eb7 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 19:52:16 -0500 Subject: [PATCH 146/190] add config generated boolean to matches models --- .../migrations/0006_match_config_generated.py | 18 ++++++++++++++++++ matches/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 matches/migrations/0006_match_config_generated.py diff --git a/matches/migrations/0006_match_config_generated.py b/matches/migrations/0006_match_config_generated.py new file mode 100644 index 000000000..6788a28b0 --- /dev/null +++ b/matches/migrations/0006_match_config_generated.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-31 22:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0005_auto_20201215_1425'), + ] + + operations = [ + migrations.AddField( + model_name='match', + name='config_generated', + field=models.BooleanField(default=False), + ), + ] diff --git a/matches/models.py b/matches/models.py index 9ae1784a4..3558fea00 100644 --- a/matches/models.py +++ b/matches/models.py @@ -164,6 +164,7 @@ class Match(models.Model): reported = models.BooleanField(default=False) # simple bool field to see if the entire match is completed completed = models.BooleanField(default=False) + config_generated = models.BooleanField(default=False) # field to declare the winner winner = models.ForeignKey(Team, related_name='champions', on_delete=models.SET_NULL, null=True, blank=True) loser = models.ForeignKey(Team, related_name='loser', on_delete=models.SET_NULL, null=True, blank=True) From fd2f2e395b29e4616e673e163fdab5cf9afdccf2 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 19:52:33 -0500 Subject: [PATCH 147/190] show generated config if generated on staff match detail --- project-templates/staff/matches/match_detail.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project-templates/staff/matches/match_detail.html b/project-templates/staff/matches/match_detail.html index b82220ecf..9f4bb577a 100644 --- a/project-templates/staff/matches/match_detail.html +++ b/project-templates/staff/matches/match_detail.html @@ -115,7 +115,11 @@
- + {% if match.config_generated %} + View Match Config + {% else %} + Generate Match Config + {% endif %}

{% if not match.completed %} Declare Winner From bd507ba2ee5f29ee4832522031db82102c389231 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 19:53:01 -0500 Subject: [PATCH 148/190] add generate config staff url --- staff/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/staff/urls.py b/staff/urls.py index 47a2580bc..294d7aa55 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -80,6 +80,7 @@ path('match//dispute', login_required(views.set_dispute_match), name='match_create_dispute'), path('match//delete', login_required(views.match_delete_winner), name='match_delete_winner'), path('match//map-generate', login_required(views.pick_map), name='match_pickmap'), + path('match//generate-cfg', login_required(views.create_match_config), name='generate_cfg'), path('match//edit', login_required(views.match_edit), name='match_edit'), path('round//edit', login_required(views.edit_round), name='edit_round'), path('round//', login_required(views.round_detail), name='round_detail'), From c4efc1819b7a6c52ff617b8ff1294b6f4ed3f9a5 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 20:08:46 -0500 Subject: [PATCH 149/190] begin work on get5 match config --- staff/views/matches.py | 90 +++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index 28c03255e..fb1a7c4ad 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -9,6 +9,8 @@ from profiles.models import UserProfile, Notification from django.core.mail import EmailMessage import datetime +import json +from random import randint from django.contrib.sites.shortcuts import get_current_site @@ -641,7 +643,6 @@ def pick_map(request, pk): """ - def match_checkins(request, pk): user = UserProfile.objects.get(user__username=request.user.username) allowed = ['superadmin', 'admin'] @@ -668,7 +669,8 @@ def delete_checkin(request, pk, checkinid): def match_stats_create(request, pk): pass - + + def set_dispute_match(request, pk): # set the specific match as disputed @@ -681,20 +683,12 @@ def set_dispute_match(request, pk): stats = MatchStats() team1 = match.awayteam team2 = match.hometeam - - -def create_match_config(request, pk): - user = UserProfile.objects.get(user__username=request.user.username) - allowed = ['superadmin', 'admin'] - if user.user_type not in allowed: - return render(request, 'staff/permissiondenied.html') - else: - # create the get5 config for the match - match.disputed = True + # match.disputed = True for i in [match.team1.players, match.team2.players]: - temp = Notification(title="A staff member set one of your matches as disputed") - temp.link = 'matches:detail' - temp.pk1 = match.pk + temp = Notification(title="A match you're playing in is disputed!", description="Captains, please visit " + "the match page for more" + "details on resolving this", + sender="Match Manager", type='match', link='match:detail', pk1=match.pk) temp.datetime = datetime.datetime.now() temp.save() userprofile = UserProfile.objects.get(user=i.user) @@ -714,7 +708,73 @@ def create_match_config(request, pk): mail_subject, message, from_email=settings.FROM_EMAIL, to=[to_email] ) email.send() + + dispute = MatchDispute(id=match.id, match=match, team1=match.team1, team2=match.team2) dispute.save() messages.success(request, "Set the match as disputed, notified users, and created the Match Dispute") return redirect('staff:match_detail', pk=match.id) + + +def create_match_config(request, pk): + user = UserProfile.objects.get(user__username=request.user.username) + allowed = ['superadmin', 'admin'] + if user.user_type not in allowed: + return render(request, 'staff/permissiondenied.html') + else: + # create the get5 config for the match + match = Match.objects.get(pk=pk) + if match.config_generated: + messages.error(request, "A config has already been generated for this match") + return redirect('staff:match_detail', pk=match.pk) + if match.awayteam.tag is None: + messages.error(request, "Away Team has no Team Tag set! Have the captain/founder add a team tag on the" + " edit team page") + return redirect('staff:match_detail', pk=match.pk) + elif match.hometeam.tag is None: + messages.error(request, "Home Team has no Team Tag set! Have the captain/founder add a team tag on the" + " edit team page", pk=match.pk) + data = {} + data['matchid'] = match.pk + data['num_maps'] = 1 + data['players_per_team'] = 5 + data['min_players_to_ready'] = 5 + data['min_spectators_to_ready'] = 0 + data['skip_veto'] = False + vetofirst = randint(1, 2) + if vetofirst == 1: + data['veto_first'] = 'team1' + else: + data['veto_first'] = 'team2' + + data['side_type'] = "standard" + data['favored_percentage_team1'] = 50 + data['favored_percentage_team2'] = 50 + data['favored_percentage_text'] = "CSC Website Predictions" + + data['maplist'] = ['de_dust2', 'de_inferno', 'de_mirage', 'de_nuke', 'de_overpass', 'de_train', 'de_vertigo'] + # todo: test tag, name, country code is 2 letter country + # get away team checkin + awayplayers = [] + awaycheck = MatchCheckIn.objects.get(match=match, team=match.awayteam) + for x in awaycheck.players.all(): + # get each player that checked in steamid + try: + temp = UserProfile.objects.get(user=x) + awayplayers.add(temp.steamid64) + except: + messages.error(request, "An error occurred finding a checkedin players profile/steamid") + return redirect('matches:detail', pk=match.pk) + # get home team checkin + homecheck = MatchCheckIn.objects.get(match=match, team=match.hometeam) + for y in homecheck.players.all(): + # get each player that checked in steamid + try: + temp = UserProfile.objects.get(user=y) + awayplayers.add(temp.steamid64) + except: + messages.error(request, "An error occurred finding a checkedin players profile/steamid") + return redirect('matches:detail', pk=match.pk) + data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code, 'players': {} } + + pass From 994455c985e4be69ef1be741b24c33aa7adb7a14 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 20:08:58 -0500 Subject: [PATCH 150/190] add team tag field --- teams/migrations/0003_team_tag.py | 18 ++++++++++++++++++ teams/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 teams/migrations/0003_team_tag.py diff --git a/teams/migrations/0003_team_tag.py b/teams/migrations/0003_team_tag.py new file mode 100644 index 000000000..ce155a705 --- /dev/null +++ b/teams/migrations/0003_team_tag.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2020-12-31 23:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0002_auto_20201215_1425'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='tag', + field=models.CharField(max_length=10, null=True), + ), + ] diff --git a/teams/models.py b/teams/models.py index ccd2842f5..e8a0664c0 100644 --- a/teams/models.py +++ b/teams/models.py @@ -8,6 +8,7 @@ class Team(models.Model): # team name name = models.CharField(max_length=25) + tag = models.CharField(max_length=10, null=True) # team bio about_us = models.CharField(max_length=250, default='Forever a mystery', blank=True) # not sure if we really need the earnings, but its here for now... From 24a0abd84c36470c7e928e1d1774c1900a7a330e Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 31 Dec 2020 20:10:58 -0500 Subject: [PATCH 151/190] add team tag to team edit --- project-templates/teams/team_edit.html | 3 +++ teams/forms.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/project-templates/teams/team_edit.html b/project-templates/teams/team_edit.html index 0aea3ca83..766d10a38 100644 --- a/project-templates/teams/team_edit.html +++ b/project-templates/teams/team_edit.html @@ -10,6 +10,9 @@ {{ field.errors }} + +

{{ form.tag }}
+
{{ form.about_us }}
diff --git a/teams/forms.py b/teams/forms.py index 6f0fef16e..335af876c 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -69,6 +69,7 @@ class Meta: model = Team fields = ( 'about_us', + 'tag', 'website', 'twitter', 'twitch', @@ -79,6 +80,7 @@ class Meta: def __init__(self, *args, **kwargs): super(EditTeamProfileForm, self).__init__(*args, **kwargs) self.fields['about_us'].widget.attrs.update({'name': 'about_us', 'class': 'form-control', 'style': 'width:30%'}) + self.fields['tag'].widget.attrs.update({'name': 'about_us', 'class': 'form-control', 'style': 'width:30%'}) self.fields['website'].widget.attrs.update({'name': 'website', 'class': 'form-control', 'style': 'width:30%'}) self.fields['twitter'].widget.attrs.update({'name': 'twitter', 'class': 'form-control', 'style': 'width:30%'}) self.fields['twitch'].widget.attrs.update({'name': 'twitch', 'class': 'form-control', 'style': 'width:30%'}) From c3dd42301d550b0df0a1171a9480faf06b0f09ac Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 1 Jan 2021 12:14:50 -0500 Subject: [PATCH 152/190] add alternate name to userprofiles --- profiles/forms.py | 2 ++ .../0003_userprofile_alternate_name.py | 18 ++++++++++++++++++ profiles/models.py | 1 + project-templates/profiles/edit_profile.html | 3 +++ 4 files changed, 24 insertions(+) create mode 100644 profiles/migrations/0003_userprofile_alternate_name.py diff --git a/profiles/forms.py b/profiles/forms.py index d60e4910e..eac8eea7c 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -29,6 +29,7 @@ class Meta: fields = ( 'profile_picture', 'about_me', + 'alternate_name', 'xbl', 'psn', 'steam', @@ -52,6 +53,7 @@ def __init__(self, *args, **kwargs): {'name': 'profile_picture', 'class': 'form-control', 'style': ''}) self.fields['about_me'].widget.attrs.update({'name': 'about_me', 'class': 'form-control', 'style': ''}) self.fields['xbl'].widget.attrs.update({'name': 'xbl', 'class': 'form-control', 'style': ''}) + self.fields['alternate_name'].widget.attrs.update({'name': 'xbl', 'class': 'form-control', 'style': ''}) self.fields['psn'].widget.attrs.update({'name': 'psn', 'class': 'form-control', 'style': ''}) self.fields['steam'].widget.attrs.update({'name': 'steam', 'class': 'form-control', 'style': diff --git a/profiles/migrations/0003_userprofile_alternate_name.py b/profiles/migrations/0003_userprofile_alternate_name.py new file mode 100644 index 000000000..8c22da21e --- /dev/null +++ b/profiles/migrations/0003_userprofile_alternate_name.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.15 on 2021-01-01 17:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0002_auto_20201214_1845'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='alternate_name', + field=models.CharField(blank=True, max_length=50, null=True), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 0c671041d..cd42d00bb 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -36,6 +36,7 @@ def __str__(self): # associate the userprofile with the django user user = models.OneToOneField(User, related_name='user', on_delete=models.CASCADE) + alternate_name = models.CharField(blank=True, null=True, max_length=50) # xp they have from winning events xp = models.PositiveSmallIntegerField(default=0) # all notifications associated with this user diff --git a/project-templates/profiles/edit_profile.html b/project-templates/profiles/edit_profile.html index cc7ca36f5..e308bc41e 100644 --- a/project-templates/profiles/edit_profile.html +++ b/project-templates/profiles/edit_profile.html @@ -16,6 +16,9 @@
{{ form.about_me }}
+ +
{{ form.alternate_name }}
+
{{ form.xbl }}
From 840a2a4543b364441288a407f5f6da020f7ff63b Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 1 Jan 2021 14:49:49 -0500 Subject: [PATCH 153/190] staff match detail template improvements --- project-templates/staff/matches/match_detail.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project-templates/staff/matches/match_detail.html b/project-templates/staff/matches/match_detail.html index 9f4bb577a..8860b126c 100644 --- a/project-templates/staff/matches/match_detail.html +++ b/project-templates/staff/matches/match_detail.html @@ -60,9 +60,11 @@ Maps: + {% for map in match.maps.all %} - {{ map.name }} + {{ map.name }} {% endfor %} + Choosen maps From 800c3e48f9989cbfd9604d215cd14f3804d6c5dd Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 1 Jan 2021 15:04:30 -0500 Subject: [PATCH 154/190] add server and datetime information for matches. --- matches/migrations/0007_auto_20210101_1444.py | 23 +++++++++++++++++++ matches/models.py | 5 ++-- .../staff/matches/match_detail.html | 14 +++++++++++ .../staff/matches/match_edit.html | 21 +++++++++++++++-- staff/forms.py | 6 ++++- staff/views/matches.py | 5 ++-- 6 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 matches/migrations/0007_auto_20210101_1444.py diff --git a/matches/migrations/0007_auto_20210101_1444.py b/matches/migrations/0007_auto_20210101_1444.py new file mode 100644 index 000000000..f013159ff --- /dev/null +++ b/matches/migrations/0007_auto_20210101_1444.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.15 on 2021-01-01 19:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('matches', '0006_match_config_generated'), + ] + + operations = [ + migrations.AddField( + model_name='match', + name='datetime', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='match', + name='server', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/matches/models.py b/matches/models.py index 3558fea00..0dfc2958b 100644 --- a/matches/models.py +++ b/matches/models.py @@ -180,8 +180,9 @@ class Match(models.Model): team2reportedwinner = models.ForeignKey(Team, related_name='team2reportedwinner', on_delete=models.SET_NULL, null=True, blank=True) - # TODO: implement datetime field for matches - # datetime = models.DateTimeField(null=True) + server = models.CharField(blank=True, null=True, max_length=255) + + datetime = models.DateTimeField(null=True, blank=True) info = models.TextField(default="Match Info: ") diff --git a/project-templates/staff/matches/match_detail.html b/project-templates/staff/matches/match_detail.html index 8860b126c..8a93bf185 100644 --- a/project-templates/staff/matches/match_detail.html +++ b/project-templates/staff/matches/match_detail.html @@ -35,6 +35,20 @@ Platform {{ match.platform.name }} + + Scheduled time + {% if match.datetime is None %} + Not scheduled yet, click here to schedule + {% else %} + + UTC: {{ match.datetime }}
+ Local: + {% endif %} + Game {{ match.game.name }} diff --git a/project-templates/staff/matches/match_edit.html b/project-templates/staff/matches/match_edit.html index 5b5d0e76e..8bce50e43 100644 --- a/project-templates/staff/matches/match_edit.html +++ b/project-templates/staff/matches/match_edit.html @@ -38,15 +38,32 @@ {{ form.info }}
+ + {{ form.server }} +
+ +
+
+ + {{ form.datetime }} +
+
- + {{ form.bestof }}
- + {{ form.disable_userreport }}
+ {% endblock %} \ No newline at end of file diff --git a/staff/forms.py b/staff/forms.py index a468ca8e9..a480c6a4c 100644 --- a/staff/forms.py +++ b/staff/forms.py @@ -225,7 +225,11 @@ class Meta: class EditMatchForm(forms.ModelForm): class Meta: model = Match - fields = ('info', 'disable_userreport', 'bestof') + fields = ('info', 'disable_userreport', 'bestof', 'server', 'datetime') + widgets = { + 'datetime': forms.DateTimeInput( + attrs={'class': 'form-control datetimepicker-input', 'id': 'datetimepicker1', + 'data-toggle': 'datetimepicker', 'data-target': '#datetimepicker1'})} class GameChoiceForm(forms.ModelForm): diff --git a/staff/views/matches.py b/staff/views/matches.py index fb1a7c4ad..624f06cbd 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -62,6 +62,7 @@ def match_edit(request, pk): if user.user_type not in allowed: return render(request, 'staff/permissiondenied.html') else: + time = datetime.datetime.utcnow() if request.method == 'POST': matchobj = Match.objects.get(pk=pk) form = EditMatchForm(request.POST, instance=matchobj) @@ -70,11 +71,11 @@ def match_edit(request, pk): messages.success(request, 'Match has been updated') return redirect('staff:match_detail', pk=pk) else: - return render(request, 'staff/matches/match_edit.html', {'form': form}) + return render(request, 'staff/matches/match_edit.html', {'form': form, 'time': time, 'pk': pk}) else: matchobj = Match.objects.get(pk=pk) form = EditMatchForm(instance=matchobj) - return render(request, 'staff/matches/match_edit.html', {'form': form, 'pk': pk}) + return render(request, 'staff/matches/match_edit.html', {'form': form, 'pk': pk, 'time': time}) class MatchDeclareWinner(View): From 563bbae458b0736749edac8110d4dccb86d15eb4 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 1 Jan 2021 15:04:46 -0500 Subject: [PATCH 155/190] more progress on auto generating match configs --- staff/views/matches.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index 624f06cbd..01c9ce1fe 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -734,7 +734,8 @@ def create_match_config(request, pk): return redirect('staff:match_detail', pk=match.pk) elif match.hometeam.tag is None: messages.error(request, "Home Team has no Team Tag set! Have the captain/founder add a team tag on the" - " edit team page", pk=match.pk) + " edit team page") + return redirect('staff:match_detail', pk=match.pk) data = {} data['matchid'] = match.pk data['num_maps'] = 1 @@ -742,11 +743,8 @@ def create_match_config(request, pk): data['min_players_to_ready'] = 5 data['min_spectators_to_ready'] = 0 data['skip_veto'] = False - vetofirst = randint(1, 2) - if vetofirst == 1: - data['veto_first'] = 'team1' - else: - data['veto_first'] = 'team2' + # TEAM2 IS HOME + data['veto_first'] = 'team2' data['side_type'] = "standard" data['favored_percentage_team1'] = 50 @@ -768,14 +766,25 @@ def create_match_config(request, pk): return redirect('matches:detail', pk=match.pk) # get home team checkin homecheck = MatchCheckIn.objects.get(match=match, team=match.hometeam) + homeplayers = [] for y in homecheck.players.all(): # get each player that checked in steamid try: temp = UserProfile.objects.get(user=y) - awayplayers.add(temp.steamid64) + homeplayers.add(temp.steamid64) except: messages.error(request, "An error occurred finding a checkedin players profile/steamid") return redirect('matches:detail', pk=match.pk) - data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code, 'players': {} } + if match.awayteam.country == "": + data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': "US"} + messages.error(request, "Away Team has no country set, using US as default") + else: + data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code} + data['team1']['players'] = {} + data['team2'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code} + data['team2']['players'] = {} + for x in awayplayers: + data['team1']['players'][x.steamid64] = x.alternate_name + for y in homeplayers: + data['team2']['players'][y.steamid64] = y.alternate_name - pass From 9be525d42f070021fa1c89f2501101b0c2f10704 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Fri, 1 Jan 2021 19:36:32 -0500 Subject: [PATCH 156/190] Missing } --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 10222816a..803d8223c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,3 +58,4 @@ pipeline { } } +} From 48e3a661a4d4a6910ed6814c689ace153358e3ad Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 6 Jan 2021 17:42:10 -0500 Subject: [PATCH 157/190] match checkin form debug @techlover1 --- matches/forms.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/matches/forms.py b/matches/forms.py index 69eb41888..b3c3c9d6a 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -40,12 +40,18 @@ class Meta: class TeamCheckInForm(forms.Form): + class Meta: + model = Team + #TODO: allow forfeit before match starts #TODO: allow request for sub player - players = forms.ModelMultipleChoiceField(queryset=None) + #players = forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple,) + players = forms.CheckboxSelectMultiple() #forfeit = forms.Che def __init__(self, request, team): - mylist = team.players.all() | team.captain.all() # | team.founder + mylist = team.players.all() | team.captain.all() #| team.founder super().__init__() - self.fields['players'].widget.attrs.update({'name': 'players', 'class': 'form-control'}) - self.fields['players'].queryset = mylist + self.players.choices = mylist + #self.fields['players'].widget.attrs.update({'name': 'players', 'class': 'form-control'}) + # self.fields['players'].queryset = mylist + # self.fields['players'].queryset = mylist From 0bdb8787938c2b5f10a67d13ee46a23f39b8fa69 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Wed, 6 Jan 2021 19:07:06 -0500 Subject: [PATCH 158/190] template spelling --- project-templates/matches/match_checkin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project-templates/matches/match_checkin.html b/project-templates/matches/match_checkin.html index 2c6fc0045..e191f8d80 100644 --- a/project-templates/matches/match_checkin.html +++ b/project-templates/matches/match_checkin.html @@ -32,6 +32,6 @@

Please note only captains and team founder's can checkin for matches

To proceed with match checkin process, please click the corresponding button below.
You will be redirected to a new page where you will select the players that will be playing in the match

- - + + {% endblock %} \ No newline at end of file From 5ea444612dc8cfd45c19e955146374d13b57a444 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Wed, 6 Jan 2021 20:28:43 -0500 Subject: [PATCH 159/190] Complete team checkin --- matches/forms.py | 21 +++++++++---------- matches/views.py | 23 ++++++++++++++------- project-templates/matches/team_checkin.html | 4 ++-- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/matches/forms.py b/matches/forms.py index b3c3c9d6a..d26da3be7 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -39,19 +39,18 @@ class Meta: fields = ['teamproof_1', 'teamproof_2', 'teamproof_3'] -class TeamCheckInForm(forms.Form): +class TeamCheckInFormGet(forms.Form): class Meta: model = Team - #TODO: allow forfeit before match starts - #TODO: allow request for sub player - #players = forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple,) - players = forms.CheckboxSelectMultiple() - #forfeit = forms.Che - def __init__(self, request, team): - mylist = team.players.all() | team.captain.all() #| team.founder + players = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple) + + def __init__(self, team): + mylist = team.players.all().values_list("pk", "username") | team.captain.all().values_list("pk", "username") super().__init__() - self.players.choices = mylist - #self.fields['players'].widget.attrs.update({'name': 'players', 'class': 'form-control'}) - # self.fields['players'].queryset = mylist + self.fields['players'].choices = mylist # self.fields['players'].queryset = mylist + + +class TeamCheckInFormPost(forms.Form): + players = forms.Form() diff --git a/matches/views.py b/matches/views.py index 417f3a06f..25b4dbe8d 100644 --- a/matches/views.py +++ b/matches/views.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib import messages +from django.contrib.auth.models import User from django.contrib.sites.shortcuts import get_current_site from django.core.mail import EmailMessage from django.shortcuts import get_object_or_404 @@ -9,7 +10,7 @@ from django.db.models import Q from matches.models import Match, MatchReport, MatchDispute, MapPoolChoice, MatchCheckIn from teams.models import Team, TeamInvite -from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm, TeamCheckInForm +from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm, TeamCheckInFormGet, TeamCheckInFormPost from profiles.models import Notification, UserProfile import datetime @@ -254,7 +255,7 @@ def match_checkin(request, pk): user = request.user away = match.awayteam home = match.hometeam - if (user in away.captain.all() or user in home.captain.all()) or (user is away.founder and user is home.founder): + if (user in away.captain.all() or user in home.captain.all()) or (user == away.founder or user == home.founder): return render(request, 'matches/match_checkin.html', {'match': match}) else: messages.error(request, "222You don't have permission to checkin for this match") @@ -274,27 +275,35 @@ def team_checkin(request, pk, teamid): # get logged in user profile = UserProfile.objects.get(user__username=request.user.username) user = request.user - if user is not team.founder and user not in team.captain.all(): + if user == team.founder: + pass + elif user == team.captain.all(): + pass + else: # they don't have perms - gtfo messages.error(request, "FFFFFYou do not have permissions to checkin this team") return redirect('matches:detail', pk=pk) if request.method == 'GET': # send the form, render - form = TeamCheckInForm(team=team, request=request.GET) + form = TeamCheckInFormGet(team=team) return render(request, 'matches/team_checkin.html', {'form': form, 'match': match, 'teamid': teamid}) elif request.method == 'POST': # lets make it and get the data # TODO: find a way to get the team instance in the form - form = TeamCheckInForm(request=request.POST, team=team) + form = TeamCheckInFormPost(request.POST) if form.is_valid(): temp = MatchCheckIn() + temp.save() temp.match = match temp.team = team temp.reporter = user # TODO: verify posted data from form of field 'players' - temp.players = form.cleaned_data['players'] + for playerid in form.data.getlist('players'): + playerid = int(playerid) + player = User.objects.get(pk=playerid) + temp.players.add(player) temp.save() - messages.success(request, 'Your team has been checked in, checkin #' + temp.pk) + messages.success(request, 'Your team has been checked in, checkin #' + str(temp.pk)) return redirect('matches:detail', pk=match.pk) else: print('dammit') diff --git a/project-templates/matches/team_checkin.html b/project-templates/matches/team_checkin.html index b22ca9198..0f1b5e3b4 100644 --- a/project-templates/matches/team_checkin.html +++ b/project-templates/matches/team_checkin.html @@ -8,13 +8,13 @@ {% block body %}

Please select the players from your team below that are playing in the match

-
+ {% csrf_token %} {{ form.as_p }}
- +
From 96054696e00fd29110e21452319924e6fc022b0c Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 12:41:00 -0500 Subject: [PATCH 160/190] Fix error message string --- matches/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matches/views.py b/matches/views.py index 25b4dbe8d..0512cb944 100644 --- a/matches/views.py +++ b/matches/views.py @@ -258,7 +258,7 @@ def match_checkin(request, pk): if (user in away.captain.all() or user in home.captain.all()) or (user == away.founder or user == home.founder): return render(request, 'matches/match_checkin.html', {'match': match}) else: - messages.error(request, "222You don't have permission to checkin for this match") + messages.error(request, "You don't have permission to checkin for this match") return redirect('matches:detail', pk=pk) """elif : From 0e2a4e9904845826510e010e4f846504f91a30f1 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 14:20:43 -0500 Subject: [PATCH 161/190] fix codacy issues --- matches/forms.py | 6 +----- matches/views.py | 8 +------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/matches/forms.py b/matches/forms.py index d26da3be7..bf3ebddaa 100644 --- a/matches/forms.py +++ b/matches/forms.py @@ -1,6 +1,5 @@ from django import forms - -from matches.models import MatchReport, MatchDispute, Match, MatchCheckIn +from matches.models import MatchReport, MatchDispute, Match from teams.models import Team @@ -30,9 +29,6 @@ class Meta: class DisputeCreateForm(forms.ModelForm): - # teamproof_1 = forms.URLField() - # teamproof_2 = forms.URLField() - # teamproof_3 = forms.URLField() class Meta: model = MatchDispute diff --git a/matches/views.py b/matches/views.py index 0512cb944..20591c5d3 100644 --- a/matches/views.py +++ b/matches/views.py @@ -7,9 +7,8 @@ from django.shortcuts import render, redirect from django.template.loader import render_to_string from django.views.generic import DetailView, CreateView, View -from django.db.models import Q from matches.models import Match, MatchReport, MatchDispute, MapPoolChoice, MatchCheckIn -from teams.models import Team, TeamInvite +from teams.models import Team from .forms import MatchReportCreateFormGet, MatchReportCreateFormPost, DisputeCreateForm, TeamCheckInFormGet, TeamCheckInFormPost from profiles.models import Notification, UserProfile import datetime @@ -261,11 +260,6 @@ def match_checkin(request, pk): messages.error(request, "You don't have permission to checkin for this match") return redirect('matches:detail', pk=pk) - """elif : - messages.error(request, "You don't have permission to checkin for this match") - return redirect('matches:detail', pk=pk) - """ - def team_checkin(request, pk, teamid): # get the team they're trying to checkin from the get From 71dc4ed1a04edbdde35f8782821ef7d1242c621a Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 14:21:07 -0500 Subject: [PATCH 162/190] re add news tests --- news/tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/news/tests.py b/news/tests.py index a55599cb3..787871c44 100644 --- a/news/tests.py +++ b/news/tests.py @@ -4,7 +4,7 @@ from .views import * -"""class PostTestCase1(TestCase): +class PostTestCase1(TestCase): fixtures = ['pages.json'] def setUp(self): @@ -50,4 +50,3 @@ def test_list2response(self): response = post_list(request) self.assertEqual(response.status_code, 200) -""" \ No newline at end of file From 9bf51e8357b863cba257577bc3e8e8eaa98ed3e3 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 14:24:34 -0500 Subject: [PATCH 163/190] fix tests --- news/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/news/tests.py b/news/tests.py index 787871c44..79ae234c8 100644 --- a/news/tests.py +++ b/news/tests.py @@ -5,7 +5,6 @@ class PostTestCase1(TestCase): - fixtures = ['pages.json'] def setUp(self): self.factory = RequestFactory() From 04c62e3c0cfb79d0b50fb717d9dedcc488cfeed1 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 14:25:19 -0500 Subject: [PATCH 164/190] readd staff tests --- staff/tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/staff/tests.py b/staff/tests.py index 65a8e1aec..3ea4d7fa1 100644 --- a/staff/tests.py +++ b/staff/tests.py @@ -2,9 +2,8 @@ from .views import * -""" + class StaffBasicTest1(TestCase): - fixtures = ['pages.json'] def setUp(self): self.factory = RequestFactory() @@ -198,4 +197,4 @@ def test_partner_list(self): response = users(request) self.assertEqual(response.status_code, 200) print('Completed staff:partner_list') -""" + From 4c178b5a75cfb79e7d709eb7df366abb115bdbd5 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Thu, 7 Jan 2021 14:54:14 -0500 Subject: [PATCH 165/190] 6 test cases for team functionality and team detail view --- teams/tests.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/teams/tests.py b/teams/tests.py index 7ce503c2d..929368265 100644 --- a/teams/tests.py +++ b/teams/tests.py @@ -1,3 +1,57 @@ -from django.test import TestCase +from django.test import TestCase, RequestFactory +from .models import Team +from django.contrib.auth.models import User +from .views import * -# Create your tests here. + +class TeamTestCase(TestCase): + def setUp(self): + self.user = User.objects.create(username="username", password="blank") + self.team = Team.objects.create(name="Team1") + self.team.founder = self.user + self.factory = RequestFactory() + + def test_founder(self): + self.assertEqual(self.team.founder, self.user) + + def test_player(self): + user2 = User.objects.create(username="player1", password="blank") + self.team.players.add(user2) + self.team.save() + self.assertIn(user2, self.team.players.all()) + + def test_2players(self): + user2 = User.objects.create(username="player1", password="blank") + user3 = User.objects.create(username="player2", password="blank") + self.team.players.add(user2) + self.team.players.add(user3) + self.team.save() + self.assertIn(user2, self.team.players.all()) + self.assertIn(user3, self.team.players.all()) + + def test_removeplayer(self): + user2 = User.objects.create(username="player1", password="blank") + self.team.players.add(user2) + self.team.save() + self.assertIn(user2, self.team.players.all()) + self.team.players.remove(user2) + self.assertNotIn(user2, self.team.players.all()) + + def test_remove2players(self): + user2 = User.objects.create(username="player1", password="blank") + user3 = User.objects.create(username="player2", password="blank") + self.team.players.add(user2) + self.team.players.add(user3) + self.team.save() + self.assertIn(user2, self.team.players.all()) + self.assertIn(user3, self.team.players.all()) + self.team.players.remove(user2) + self.team.players.remove(user3) + self.assertNotIn(user2, self.team.players.all()) + self.assertNotIn(user3, self.team.players.all()) + + def test_teamdetailview(self): + request = self.factory.get('teams:detail', pk=self.team.id) + request.user = self.user + response = MyTeamDetailView.get(None, request, pk=self.team.id) + self.assertEqual(response.status_code, 200) From f146af74c1eabb6299d5e6fdc157e819363afa21 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Thu, 7 Jan 2021 20:38:32 -0500 Subject: [PATCH 166/190] Remove unused imports and refactor 'map' variable --- leagues/views.py | 6 +-- profiles/views.py | 1 - singletournaments/views.py | 3 +- staff/views/matches.py | 91 ++++++++++++++++++-------------------- teams/forms.py | 2 - teams/views.py | 1 - 6 files changed, 48 insertions(+), 56 deletions(-) diff --git a/leagues/views.py b/leagues/views.py index 6be29bb7b..97a600953 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -1,8 +1,9 @@ from django.shortcuts import render, get_object_or_404 from django.contrib import messages from django.shortcuts import redirect -from .models import League, LeagueDivision, LeagueSettings -from profiles.models import User, UserProfile +from .models import League, LeagueDivision +from profiles.models import UserProfile + def list_leagues(request): leagues = League.objects.filter(active=True) @@ -43,7 +44,6 @@ def join_league(request, pk): return redirect('leagues:detail', pk=pk) # now lets check that the team has enough players - return render(request, 'leagues/league_join.html', {'league': league}) diff --git a/profiles/views.py b/profiles/views.py index 8339b6ecc..fa6a01819 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -16,7 +16,6 @@ from django.utils.encoding import force_bytes, force_text from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode, is_safe_url from django.views.generic import View -import datetime from teams.models import TeamInvite from .forms import CreateUserForm, EditProfileForm, SortForm from .models import UserProfile, Notification diff --git a/singletournaments/views.py b/singletournaments/views.py index c3d044249..9f2627300 100644 --- a/singletournaments/views.py +++ b/singletournaments/views.py @@ -7,11 +7,10 @@ from profiles.models import UserProfile from store.models import deduct_credits, give_credits -from teams.models import TeamInvite, Team +from teams.models import Team from .forms import SingleEliminationTournamentJoinGet, SingleEliminationTournamentJoinPost, \ SingleEliminationTournamentSort, SingleTournamentLeaveForm from .models import SingleTournamentRound, SingleEliminationTournament, SingleTournamentTeam -from pages.models import Partner, StaticInfo class List(View): diff --git a/staff/views/matches.py b/staff/views/matches.py index 01c9ce1fe..38d29aa14 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -9,8 +9,6 @@ from profiles.models import UserProfile, Notification from django.core.mail import EmailMessage import datetime -import json -from random import randint from django.contrib.sites.shortcuts import get_current_site @@ -513,8 +511,8 @@ def pick_map(request, pk): # make sure the mappoolchoice is big enough pool = match.map_pool.maps if pool.count() >= 1: - map = pool.all().order_by("?").first() - match.maps.add(map) + chosen_map = pool.all().order_by("?").first() + match.maps.add(chosen_map) match.save() messages.success(request, "Maps updated!") return redirect('staff:match_detail', pk=pk) @@ -525,13 +523,13 @@ def pick_map(request, pk): # bo2 pool = match.map_pool.maps if pool.count() >= 2: - map = pool.all().order_by("?").first() - match.maps.add(map) + chosen_map = pool.all().order_by("?").first() + match.maps.add(chosen_map) match.save() - pool.remove(map) + pool.remove(chosen_map) #pool.save() - map2 = pool.all().order_by("?").first() - match.maps.add(map2) + chosen_map2 = pool.all().order_by("?").first() + match.maps.add(chosen_map2) match.save() messages.success(request, "Maps updated!") return redirect('staff:match_detail', pk=pk) @@ -544,22 +542,22 @@ def pick_map(request, pk): # make sure the mappoolchoice is big enough pool = match.map_pool.maps if pool.count() >= 3: - map = pool.all().order_by("?").first() - pool.remove(map) + chosen_map = pool.all().order_by("?").first() + pool.remove(chosen_map) #pool.save() - match.maps.add(map) + match.maps.add(chosen_map) match.save() - map2 = pool.all().order_by("?").first() - pool.remove(map2) + chosen_map2 = pool.all().order_by("?").first() + pool.remove(chosen_map2) #pool.save() - match.maps.add(map2) + match.maps.add(chosen_map2) match.save() - map3 = pool.all().order_by("?").first() - pool.remove(map3) + chosen_map3 = pool.all().order_by("?").first() + pool.remove(chosen_map3) #pool.save() - match.maps.add(map3) + match.maps.add(chosen_map3) match.save() - match.maps.add(map3) + match.maps.add(chosen_map3) match.save() messages.success(request, "Maps updated!") return redirect('staff:match_detail', pk=pk) @@ -572,25 +570,25 @@ def pick_map(request, pk): # make sure the mappoolchoice is big enough pool = match.map_pool.maps if pool.count() >= 4: - map = pool.all().order_by("?").first() - pool.remove(map) + chosen_map = pool.all().order_by("?").first() + pool.remove(chosen_map) #pool.save() - match.maps.add(map) + match.maps.add(chosen_map) match.save() - map2 = pool.all().order_by("?").first() - pool.remove(map2) + chosen_map2 = pool.all().order_by("?").first() + pool.remove(chosen_map2) #pool.save() - match.maps.add(map2) + match.maps.add(chosen_map2) match.save() - map3 = pool.all().order_by("?").first() - pool.remove(map3) + chosen_map3 = pool.all().order_by("?").first() + pool.remove(chosen_map3) #pool.save() - match.maps.add(map3) + match.maps.add(chosen_map3) match.save() - map4 = pool.all().order_by("?").first() - pool.remove(map4) + chosen_map4 = pool.all().order_by("?").first() + pool.remove(chosen_map4) #pool.save() - match.maps.add(map4) + match.maps.add(chosen_map4) match.save() messages.success(request, "Maps updated!") return redirect('staff:match_detail', pk=pk) @@ -598,32 +596,31 @@ def pick_map(request, pk): messages.error(request, "There are not enough maps in this map pool for Best of 4") return redirect('staff:match_detail', pk=pk) - elif match.bestof == 5: # bo5 # make sure the mappoolchoice is big enough pool = match.map_pool.maps if pool.count() >= 5: - map = pool.all().order_by("?").first() - pool.remove(map) + chosen_map = pool.all().order_by("?").first() + pool.remove(chosen_map) #pool.save() - map2 = pool.all().order_by("?").first() - pool.remove(map2) + chosen_map2 = pool.all().order_by("?").first() + pool.remove(chosen_map2) #pool.save() - map3 = pool.all().order_by("?").first() - pool.remove(map3) + chosen_map3 = pool.all().order_by("?").first() + pool.remove(chosen_map3) #pool.save() - map4 = pool.all().order_by("?").first() - pool.remove(map4) + chosen_map4 = pool.all().order_by("?").first() + pool.remove(chosen_map4) #pool.save() - map5 = pool.all().order_by("?").first() - pool.remove(map5) + chosen_map5 = pool.all().order_by("?").first() + pool.remove(chosen_map5) #pool.save() - match.maps.add(map) - match.maps.add(map2) - match.maps.add(map3) - match.maps.add(map4) - match.maps.add(map5) + match.maps.add(chosen_map) + match.maps.add(chosen_map2) + match.maps.add(chosen_map3) + match.maps.add(chosen_map4) + match.maps.add(chosen_map5) match.save() messages.success(request, "Maps updated!") return redirect('staff:match_detail', pk=pk) diff --git a/teams/forms.py b/teams/forms.py index 335af876c..9ddcd2827 100644 --- a/teams/forms.py +++ b/teams/forms.py @@ -1,8 +1,6 @@ from django import forms -from django.db.models import Q # import the actual team model for the create team forms from teams.models import Team -from profiles.models import UserProfile # import the model for the team invite from teams.models import TeamInvite from profiles.models import UserProfile diff --git a/teams/views.py b/teams/views.py index 80abfb06d..d0185b1dc 100644 --- a/teams/views.py +++ b/teams/views.py @@ -10,7 +10,6 @@ from django.template.loader import render_to_string from django.urls import reverse from django.utils import timezone -from django.db.models import Q from django.views.generic import ListView, DetailView, View from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist From a1942c62a7bcec37814dd4546b8d4967dc0dbec7 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 14:11:36 -0500 Subject: [PATCH 167/190] add additional links in navbar --- project-templates/base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project-templates/base.html b/project-templates/base.html index ffc12c855..ba0fef0d3 100644 --- a/project-templates/base.html +++ b/project-templates/base.html @@ -49,6 +49,8 @@
  • My Support Tickets
  • Store
  • Teams
  • +
  • Notifications
  • +
  • Leagues
  • {% endif %}
    @@ -134,8 +134,9 @@

    Registration Fee

    - +
    +

    Please contact an admin about joining this league

    +
    From 666ed3b56f8dd9b7baab46382ea635f7763806e4 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 14:47:43 -0500 Subject: [PATCH 178/190] fix league detail rules view --- leagues/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/leagues/views.py b/leagues/views.py index 14aeefabe..1cf65ec56 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -81,4 +81,6 @@ def detail_league_division(request, pk, divid): def detail_league_rules(request, pk): - pass + league = get_object_or_404(League, pk=pk) + rules = league.ruleset + return render(request, 'singletournaments/ruleset_detail.html', {'ruleset': rules}) From e8060b6647d0e24e244ceaa16b8045cdee23d976 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 14:47:52 -0500 Subject: [PATCH 179/190] remove debug print --- leagues/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/leagues/views.py b/leagues/views.py index 1cf65ec56..f67382a2e 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -13,7 +13,6 @@ def list_leagues(request): def detail_league(request, pk): league = get_object_or_404(League, pk=pk) teams = league.teams.all() - print(league.divisions) if league.divisions is None: # there are no divisions pass From f1f75c6705d9a41ccf4e7b401816c7a9ed851d33 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 14:52:33 -0500 Subject: [PATCH 180/190] fix 404 static files --- .../webfonts/fa-brands-400.ttf | Bin 0 -> 136516 bytes .../webfonts/fa-brands-400.woff | Bin 0 -> 92136 bytes .../webfonts/fa-brands-400.woff2 | Bin 0 -> 78476 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 project-static/fontawesome5.0.4/webfonts/fa-brands-400.ttf create mode 100644 project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff create mode 100644 project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff2 diff --git a/project-static/fontawesome5.0.4/webfonts/fa-brands-400.ttf b/project-static/fontawesome5.0.4/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000000000000000000000000000000000000..937b33bd85c3c4e7f92ff054110c82537b8ef9a2 GIT binary patch literal 136516 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 literal 0 HcmV?d00001 diff --git a/project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff b/project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff new file mode 100644 index 0000000000000000000000000000000000000000..f7323ad3888833175e6b2575c1cf699189e10f69 GIT binary patch literal 92136 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%0A@Um_ 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 Gu4WLw$`>O5 literal 0 HcmV?d00001 diff --git a/project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff2 b/project-static/fontawesome5.0.4/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..9a54f6de5e959e2714e3c7d790913bbb2ab4e53b GIT binary patch literal 78476 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∾)cvaDab>1 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 Date: Fri, 8 Jan 2021 14:57:27 -0500 Subject: [PATCH 181/190] fix league teams view and template --- leagues/views.py | 13 ++++++++++++- project-templates/leagues/league_teams.html | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/leagues/views.py b/leagues/views.py index f67382a2e..58f6f5083 100644 --- a/leagues/views.py +++ b/leagues/views.py @@ -56,7 +56,18 @@ def leave_league(request, pk): def detail_league_teams(request, pk): - pass + # show all the teams in the league + # get all divisions first + league = League.objects.get(pk=pk) + if league.divisions.count() == 0: + messages.error(request, "There are no divisions or teams in this league yet, check back later") + return redirect('league:list') + teams = [] + for x in league.divisions.all(): + for y in x.teams.all(): + teams.append(y) + + return render(request, "leagues/league_teams.html", {'teams': teams, 'league': league}) def list_league_divisions(request, pk): diff --git a/project-templates/leagues/league_teams.html b/project-templates/leagues/league_teams.html index fe4b25e4b..b72f583f3 100644 --- a/project-templates/leagues/league_teams.html +++ b/project-templates/leagues/league_teams.html @@ -2,14 +2,14 @@ {% load static %} {% block head %} - Division #{{ division.id }} Matches - {{ SITE_NAME }} + {{ league.name }} Teams - {{ SITE_NAME }} {% endblock %} {% block body %} -

    Division Standings

    +

    League Teams

    From ef0c20692c86df793bd9aabb661c2cda411dfa06 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 15:03:16 -0500 Subject: [PATCH 182/190] template fix and pep8 --- project-templates/leagues/league_detail.html | 273 +++++++++---------- 1 file changed, 133 insertions(+), 140 deletions(-) diff --git a/project-templates/leagues/league_detail.html b/project-templates/leagues/league_detail.html index 7e14543a5..5cd72aaca 100644 --- a/project-templates/leagues/league_detail.html +++ b/project-templates/leagues/league_detail.html @@ -10,165 +10,156 @@ -
    +
    + + {% if not user.is_anonymous %} +

    Back to + League list

    + {% else %} + Back to home + {% endif %} +
    +
    +
    + {% if products.count == 0 %} +

    No store products! Check back later!

    + {% endif %} {% for product in products %} From 348f802d75710d19097de1e4fe6198f21baa58e0 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 15:15:16 -0500 Subject: [PATCH 184/190] add models to admin panel --- leagues/admin.py | 6 +++++- profiles/admin.py | 3 ++- singletournaments/admin.py | 5 ++++- teams/admin.py | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/leagues/admin.py b/leagues/admin.py index 8c38f3f3d..91df4bbbb 100644 --- a/leagues/admin.py +++ b/leagues/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from .models import League, LeagueDivision, LeagueTeam, LeagueSettings -# Register your models here. +admin.site.register(League) +admin.site.register(LeagueDivision) +admin.site.register(LeagueTeam) +admin.site.register(LeagueSettings) diff --git a/profiles/admin.py b/profiles/admin.py index 9a7d8062b..645432668 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from profiles.models import UserProfile +from profiles.models import UserProfile, Notification # Register your models here. admin.site.register(UserProfile) +admin.site.register(Notification) diff --git a/singletournaments/admin.py b/singletournaments/admin.py index 8a40c371d..005b878d3 100644 --- a/singletournaments/admin.py +++ b/singletournaments/admin.py @@ -1,5 +1,8 @@ from django.contrib import admin -from .models import SingleEliminationTournament +from .models import SingleEliminationTournament, SingleTournamentRound, SingleTournamentRuleset, SingleTournamentTeam admin.site.register(SingleEliminationTournament) +admin.site.register(SingleTournamentRound) +admin.site.register(SingleTournamentRuleset) +admin.site.register(SingleTournamentTeam) diff --git a/teams/admin.py b/teams/admin.py index 4d9e613e4..26bce15f1 100644 --- a/teams/admin.py +++ b/teams/admin.py @@ -4,4 +4,4 @@ # Register your models here. admin.site.register(Team) -admin.site.register(TeamInvite) \ No newline at end of file +admin.site.register(TeamInvite) From 687f2474857b73d75ff69594627d98e7d3182533 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 15:18:06 -0500 Subject: [PATCH 185/190] pep8 and add link to staff:match detail on staff league div detail --- .../staff/leagues/league_division_detail.html | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/project-templates/staff/leagues/league_division_detail.html b/project-templates/staff/leagues/league_division_detail.html index 8bae21df0..06502d6a5 100644 --- a/project-templates/staff/leagues/league_division_detail.html +++ b/project-templates/staff/leagues/league_division_detail.html @@ -27,32 +27,37 @@ - +
    Price
    Apart of League{{ league.name }} (League ID:{{ league.id }}){{ league.name }} (League ID:{{ league.id }}) +
    - + -
    - -
    +
    + +

    Matches

    - - - - - - {% for match in matches %} - - - - + + + + + + {% for match in matches %} + + + + - - {% endfor %} + + {% endfor %}
    Match IDAway TeamHome Team
    {{ match.pk }}{{ match.awayteam.name }}{{ match.hometeam.name }}
    Match IDAway TeamHome Team
    {{ match.pk }}{{ match.awayteam.name }}{{ match.hometeam.name }}

    Teams

    From 78e9662b4e98aeb3677ecf292d7b03febdba6190 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 15:32:20 -0500 Subject: [PATCH 186/190] fix staff template location --- staff/views/matches.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index 38d29aa14..a51b36137 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -77,7 +77,7 @@ def match_edit(request, pk): class MatchDeclareWinner(View): - template_name = 'staff/matches_winner.html' + template_name = 'staff/matches/matches_winner.html' def get(self, request, pk): user = UserProfile.objects.get(user__username=request.user.username) From b48be9f1952025bbf9ad8197e46b3c154cb00e65 Mon Sep 17 00:00:00 2001 From: Michael Madden Date: Fri, 8 Jan 2021 15:51:31 -0500 Subject: [PATCH 187/190] work on #107 #113 --- staff/views/matches.py | 105 ++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/staff/views/matches.py b/staff/views/matches.py index a51b36137..7d0a40a03 100644 --- a/staff/views/matches.py +++ b/staff/views/matches.py @@ -22,7 +22,8 @@ def matches_index(request): tmatches = Match.objects.filter(type="singletournament") wmatches = Match.objects.filter(type='w') lmatches = Match.objects.filter(type="leagues") - return render(request, 'staff/matches/matches.html', {'tmatches': tmatches, 'wmatches': wmatches, 'lmatches': lmatches}) + return render(request, 'staff/matches/matches.html', + {'tmatches': tmatches, 'wmatches': wmatches, 'lmatches': lmatches}) def disputed_matches(request): @@ -96,6 +97,58 @@ def post(self, request, pk): else: matchobj = Match.objects.get(pk=pk) if matchobj.type == "league": + form = DeclareMatchWinnerPost(request.POST, instance=matchobj) + instance = form.instance + match = Match.objects.get(id=self.kwargs['pk']) + winner = Team.objects.get(id=form.data['winner']) + league = None + division = None + for x in League.objects.all(): + for y in LeagueDivision.objects.all(): + if match in y.matches: + league = x + division = y + messages.success(request, "Found match league and division") + if league is None or division is None: + messages.error(request, "Failed to find league or division this match is apart of") + return redirect('staff:match_detail', pk=match.pk) + # leagueteam = division.teams.all() + awayleagueteam = None + homeleagueteam = None + for z in division.teams.all(): + if z.team == match.awayteam: + # found away team + awayleagueteam = z + if z.team == match.hometeam: + homeleagueteam = z + if awayleagueteam is None or homeleagueteam is None: + messages.error(request, "Unable to find home or away league team objects") + return redirect('staff:match_detail', pk=match.pk) + teams = list() + teams.append(match.hometeam) + teams.append(match.awayteam) + teams.remove(winner) + loser = teams[0] + instance.match = match + instance.winner = winner + instance.loser = loser + instance.completed = True + instance.save() + if loser == match.awayteam: + awayleagueteam.losses += 1 + awayleagueteam.save() + match.awayteam.num_losses += 1 + match.hometeam.num_wins += 1 + homeleagueteam.wins += 1 + homeleagueteam.save() + elif loser == match.hometeam: + awayleagueteam.wins += 1 + homeleagueteam.losses += 1 + awayleagueteam.save() + homeleagueteam.save() + match.awayteam.num_wins += 1 + match.hometeam.num_losses += 1 + # TODO: 107 pass if not matchobj.bye_2 and not matchobj.bye_1: @@ -128,6 +181,15 @@ def post(self, request, pk): wplayer = UserProfile.objects.get(user=player) wplayer.total_earning += wmatch.credits messages.success(request, 'Wager Winner declared') + for player in winner.players | winner.captain: + tp = UserProfile.objects.get(user=player) + notif = Notification(title="You won a match!", type=1, + description="A match your team was in was finalized and your team won!" + + " Time to celebrate!", link="matches:detail", pk1=match.pk) + notif.save() + tp.notifications.add(notif) + tp.save() + messages.success(request, "Winning team players and captains notified about the victory") messages.success(request, "Winner declared") except: messages.error(request, "Match statistics were not properly logged") @@ -527,7 +589,7 @@ def pick_map(request, pk): match.maps.add(chosen_map) match.save() pool.remove(chosen_map) - #pool.save() + # pool.save() chosen_map2 = pool.all().order_by("?").first() match.maps.add(chosen_map2) match.save() @@ -544,17 +606,17 @@ def pick_map(request, pk): if pool.count() >= 3: chosen_map = pool.all().order_by("?").first() pool.remove(chosen_map) - #pool.save() + # pool.save() match.maps.add(chosen_map) match.save() chosen_map2 = pool.all().order_by("?").first() pool.remove(chosen_map2) - #pool.save() + # pool.save() match.maps.add(chosen_map2) match.save() chosen_map3 = pool.all().order_by("?").first() pool.remove(chosen_map3) - #pool.save() + # pool.save() match.maps.add(chosen_map3) match.save() match.maps.add(chosen_map3) @@ -572,22 +634,22 @@ def pick_map(request, pk): if pool.count() >= 4: chosen_map = pool.all().order_by("?").first() pool.remove(chosen_map) - #pool.save() + # pool.save() match.maps.add(chosen_map) match.save() chosen_map2 = pool.all().order_by("?").first() pool.remove(chosen_map2) - #pool.save() + # pool.save() match.maps.add(chosen_map2) match.save() chosen_map3 = pool.all().order_by("?").first() pool.remove(chosen_map3) - #pool.save() + # pool.save() match.maps.add(chosen_map3) match.save() chosen_map4 = pool.all().order_by("?").first() pool.remove(chosen_map4) - #pool.save() + # pool.save() match.maps.add(chosen_map4) match.save() messages.success(request, "Maps updated!") @@ -603,19 +665,19 @@ def pick_map(request, pk): if pool.count() >= 5: chosen_map = pool.all().order_by("?").first() pool.remove(chosen_map) - #pool.save() + # pool.save() chosen_map2 = pool.all().order_by("?").first() pool.remove(chosen_map2) - #pool.save() + # pool.save() chosen_map3 = pool.all().order_by("?").first() pool.remove(chosen_map3) - #pool.save() + # pool.save() chosen_map4 = pool.all().order_by("?").first() pool.remove(chosen_map4) - #pool.save() + # pool.save() chosen_map5 = pool.all().order_by("?").first() pool.remove(chosen_map5) - #pool.save() + # pool.save() match.maps.add(chosen_map) match.maps.add(chosen_map2) match.maps.add(chosen_map3) @@ -649,7 +711,7 @@ def match_checkins(request, pk): else: mymatch = Match.objects.get(pk=pk) checkins = MatchCheckIn.objects.filter(match=mymatch) - return render(request, 'staff/matches/checkins.html', {'checkins': checkins, 'mymatch':mymatch}) + return render(request, 'staff/matches/checkins.html', {'checkins': checkins, 'mymatch': mymatch}) def delete_checkin(request, pk, checkinid): @@ -661,7 +723,7 @@ def delete_checkin(request, pk, checkinid): checkin = MatchCheckIn.objects.get(pk=pk) checkin.delete() checkin.save() - messages.success(request, "Checkin #"+checkin.pk+" has been deleted") + messages.success(request, "Checkin #" + checkin.pk + " has been deleted") return redirect('staff:index') @@ -684,9 +746,9 @@ def set_dispute_match(request, pk): # match.disputed = True for i in [match.team1.players, match.team2.players]: temp = Notification(title="A match you're playing in is disputed!", description="Captains, please visit " - "the match page for more" - "details on resolving this", - sender="Match Manager", type='match', link='match:detail', pk1=match.pk) + "the match page for more" + "details on resolving this", + sender="Match Manager", type='match', link='match:detail', pk1=match.pk) temp.datetime = datetime.datetime.now() temp.save() userprofile = UserProfile.objects.get(user=i.user) @@ -707,7 +769,6 @@ def set_dispute_match(request, pk): ) email.send() - dispute = MatchDispute(id=match.id, match=match, team1=match.team1, team2=match.team2) dispute.save() messages.success(request, "Set the match as disputed, notified users, and created the Match Dispute") @@ -776,7 +837,8 @@ def create_match_config(request, pk): data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': "US"} messages.error(request, "Away Team has no country set, using US as default") else: - data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code} + data['team1'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, + 'flag': match.awayteam.country.code} data['team1']['players'] = {} data['team2'] = {'name': match.awayteam.name, 'tag': match.awayteam.tag, 'flag': match.awayteam.country.code} data['team2']['players'] = {} @@ -784,4 +846,3 @@ def create_match_config(request, pk): data['team1']['players'][x.steamid64] = x.alternate_name for y in homeplayers: data['team2']['players'][y.steamid64] = y.alternate_name - From 71c5cf1c8bdf48137bc5f735cb8d7414e4910914 Mon Sep 17 00:00:00 2001 From: Steven Young Date: Sat, 9 Jan 2021 16:09:54 -0500 Subject: [PATCH 188/190] disable deprecated features (store and wagers) based on env var --- .env.example | 6 +++--- olly/base_settings.py | 16 ++++++++++++++ olly/context_processors.py | 4 +++- project-templates/base.html | 4 ++-- project-templates/staff/staffbase.html | 14 +++++++------ project-templates/staff/staffindex.html | 28 +++++++++++++------------ 6 files changed, 47 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index 3818b043b..4f5ff698b 100644 --- a/.env.example +++ b/.env.example @@ -29,9 +29,6 @@ storage_endpoint_url=https:// # Your email server (for example smtp.gmail.com) email_host= -# The email address you use for paypal -paypal_email= - # The login information for your email account email_host_user= email_host_password= @@ -54,6 +51,9 @@ site_name= site_server= #### Optional settings (only set these if you know what you're doing) +#enable_wagers= +#enable_store= +#paypal_email= #user_verification= #esports_mode= #static_path= diff --git a/olly/base_settings.py b/olly/base_settings.py index 6b760aa2e..29ce0a86b 100644 --- a/olly/base_settings.py +++ b/olly/base_settings.py @@ -82,6 +82,22 @@ 'django_cleanup.apps.CleanupConfig' ] +try: + if os.environ['enable_store'] == 'True': + STORE_ENABLED = True + else: + STORE_ENABLED = False +except KeyError: + STORE_ENABLED = False + +try: + if os.environ['enable_wagers'] == 'True': + WAGERS_ENABLED = True + else: + WAGERS_ENABLED = False +except KeyError: + WAGERS_ENABLED = False + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/olly/context_processors.py b/olly/context_processors.py index 17627f4e8..38fd83044 100644 --- a/olly/context_processors.py +++ b/olly/context_processors.py @@ -14,4 +14,6 @@ def site_info(request): 'SITE_SERVER': settings.SITE_SERVER, 'SITE_VERSION': settings.SITE_VERSION, 'SocialInfo': SocialInfo, - 'ESPORTS_MODE': settings.ESPORTS_MODE} + 'ESPORTS_MODE': settings.ESPORTS_MODE, + 'STORE_ENABLED': settings.STORE_ENABLED, + 'WAGERS_ENABLED': settings.WAGERS_ENABLED} diff --git a/project-templates/base.html b/project-templates/base.html index 268dc22bf..5f1ffc58c 100644 --- a/project-templates/base.html +++ b/project-templates/base.html @@ -45,9 +45,9 @@
  • Users
  • Tournaments
  • My Profile
  • -
  • Wagers
  • + {% if WAGERS_ENABLED %}
  • Wagers
  • {% endif %}
  • My Support Tickets
  • -
  • Store
  • + {% if STORE_ENABLED %}
  • Store
  • {% endif %}
  • Teams
  • Notifications
  • Leagues
  • diff --git a/project-templates/staff/staffbase.html b/project-templates/staff/staffbase.html index aa9e16793..e58e5df8e 100644 --- a/project-templates/staff/staffbase.html +++ b/project-templates/staff/staffbase.html @@ -259,12 +259,14 @@ --> - + {% if STORE_ENABLED %} + + {% endif %}