forked from 42CTF/website
Compare commits
217 Commits
Author | SHA1 | Date |
---|---|---|
Danhia | f1b1214291 | |
Danhia | 8b28f73bdb | |
Danhia | c8adc6caf9 | |
Danhia | 6ecb94eab5 | |
Danhia | 44897411f5 | |
Danhia | 5a10e033a2 | |
Danhia | 3d86f21ba2 | |
Starthur | 691c53e110 | |
Starthur | 2d4816e78f | |
Danhia | 5d84174db2 | |
Danhia | 9ad64e72bc | |
Danhia | 9d0a420cac | |
Starthur | 7120a80c1d | |
Starthur | fe71460537 | |
Danhia | 32bbc392d6 | |
UncleReaton | acf306097a | |
UncleReaton | dc955fc189 | |
UncleReaton | e304f7cd15 | |
Danhia | 73cf94515d | |
Danhia | 94237bd9b6 | |
Starthur | a983aafba2 | |
Starthur | cce09ed254 | |
Danhia | c2e58c4f92 | |
Danhia | eba672d067 | |
Danhia | 1a6c31f5e8 | |
Danhia | bd17ad5f8f | |
Danhia | 40984a2a0c | |
Danhia | 9ac003ea3c | |
Danhia | 5cf86b9c36 | |
zero | 7c06c24d9f | |
zero | 9b2da1e2cc | |
zero | 891c2530e6 | |
Danhia | 13895d7712 | |
Danhia | 0f64c7579c | |
zero | 02331beff4 | |
zero | 5b2abeac20 | |
Danhia | 1771f3c2bb | |
Danhia | 09118cac7a | |
Danhia | 7cd43ca35f | |
Danhia | d21915405e | |
zero | dd7a69aa50 | |
Danhia | 8a2cc6f3ae | |
Danhia | e36ca2b146 | |
Danhia | 63876e91ef | |
Danhia | eadede339b | |
zero | d1992e1476 | |
Danhia | 00acec6fdb | |
Danhia | 1ee02fc964 | |
Starthur | ff23250275 | |
Starthur | c449f26ad7 | |
Danhia | 42858e4342 | |
Danhia | ec61f06be0 | |
Danhia | 2233474c89 | |
Danhia | 7b9dfad15d | |
Starthur | 46ea80161d | |
Starthur | d222784bb0 | |
Starthur | 86fbf93dbf | |
Arthur-TRT | ff657c070a | |
Starthur | a1ec3e5f20 | |
Starthur | 0d7f39d335 | |
Danhia | f56322bd6e | |
Danhia | 506eb54d49 | |
Starthur | 87efa03ed0 | |
Starthur | 5435ea3316 | |
Starthur | 4245cfd19f | |
Starthur | f391a5a5cb | |
Starthur | ebdc3b9c9e | |
Starthur | 9d3b9ebf5f | |
Starthur | 04318e3b0a | |
Starthur | eeb752d605 | |
Starthur | 2d466454f5 | |
Starthur | 7ff972e623 | |
Starthur | d8791353fb | |
Starthur | dc3e7f4b27 | |
Starthur | 763853b3c6 | |
Starthur | c1c82ff9e0 | |
Starthur | fdc3796667 | |
Starthur | b25bf03485 | |
Starthur | 5a08764df7 | |
UncleReaton | 084fb1a62d | |
Starthur | d8c35db454 | |
UncleReaton | 6e860b2997 | |
Starthur | 4ee613d9af | |
Starthur | 9c75b317eb | |
Starthur | 2d2e97e794 | |
Arthur-TRT | 66ab43bf2b | |
Starthur | 4213883774 | |
Arthur-TRT | 9cb4b2b3a8 | |
Starthur | b90cc55b8d | |
UncleReaton | d19514f629 | |
Aslan | 6b2b0cef7d | |
Aslan | d7bf9ae74e | |
Danhia | dfa36b58a6 | |
Starthur | bf5678d371 | |
Danhia | 3c013794d7 | |
Danhia | 6110408cd0 | |
Starthur | bb2617a3e4 | |
Starthur | e23d6d7f49 | |
Starthur | 978dd24a00 | |
Danhia | 15f3a21c60 | |
Starthur | e480aec493 | |
Starthur | 3991dda7d3 | |
Starthur | e8575d559e | |
Starthur | ea691204c4 | |
Arthur TROUILLET | 21f38906df | |
Arthur TROUILLET | 39e3859836 | |
Arthur-TRT | a26784cd98 | |
Yir | ba7992e7be | |
Danhia | 7dad96d79f | |
Danhia | d554961dd1 | |
Danhia | 280ba0d1fc | |
Bertrand C | bdd51b6cf9 | |
Bertrand C | c004c18e75 | |
Danhia | e8d43586a6 | |
Danhia | 2da57b7b9b | |
Danhia | a828ce2ece | |
UncleReaton | 16030c14a4 | |
Aslan | ca86079148 | |
Aslan | 93c9844efa | |
Aslan | 4c06ed41bf | |
Aslan | fca3609c1b | |
Aslan | b0380d4fc3 | |
Aslan | 3ea2a41d2b | |
Aslan | de87c70ae0 | |
Aslan | 22fe0d601a | |
Danhia | 5191e300df | |
Danhia | 09377c509f | |
Danhia | 6a76f4b99f | |
ysaito | 5908ce4778 | |
Danhia | 4e5fe52984 | |
Danhia | 2f24fae611 | |
Danhia | 02385dcd08 | |
ysaito | 7b2de06225 | |
ysaito | ba1cb03dbf | |
Danhia | 11164bb84a | |
Danhia | 67a4d180e0 | |
Danhia | 3b86441259 | |
Danhia | 7b2552f0b5 | |
Danhia | eac835bef5 | |
Danhia | 06593b26f9 | |
Danhia | e8f5f33269 | |
Danhia | f0479f6577 | |
Danhia | 60a59b23e6 | |
Danhia | 8f32ef9a6e | |
Danhia | a0b76903a7 | |
Danhia | a7de7b8054 | |
Danhia | 0d734e98b1 | |
Danhia | 3d24fe9b3b | |
Danhia | ba0d75c250 | |
Danhia | 3ae80ca17f | |
Danhia | a7e75b2a43 | |
Danhia | 9ea67ae2a0 | |
Danhia | 899a01e472 | |
Danhia | c40d49c326 | |
Starthur | d6b1380552 | |
Arthur-TRT | d0c93f98a1 | |
Arthur-TRT | 97e120e5fc | |
Danhia | 494377399c | |
Starthur | 28d8874272 | |
Starthur | b9704d3cb3 | |
Danhia | 0ac395def8 | |
Danhia | 7ff556a986 | |
Danhia | 5e02041f0c | |
Danhia | f75a034094 | |
Danhia | 5f28bc5d2c | |
ysaito | 904ce1b748 | |
ysaito | 5b15b9cd6f | |
ysaito | 5d46bd6df0 | |
ysaito | 67de669459 | |
ysaito | 26aeb6d7bd | |
Danhia | 1b89fa88d8 | |
Danhia | 0bb3a8b805 | |
Danhia | 5eec76dd00 | |
Clément Hamada | 6b6f264fe0 | |
Clément Hamada | c4fd6d05c3 | |
Danhia | 901c73a4f2 | |
Danhia | 01c0f28b16 | |
Danhia | 7174cf9edb | |
Danhia | ef0fcc5fea | |
Danhia | aabba3ea53 | |
Miliviu | 1abeabebfe | |
Danhia | 9f2d78ac33 | |
Danhia | 27ed107501 | |
Miliviu | 0dea7fb619 | |
Danhia | 23529b3b07 | |
Danhia | cb9bc7cc56 | |
Danhia | af4912837f | |
Miliviu | 187579e61c | |
Miliviu | fabe417fe2 | |
Miliviu | a6a5e424df | |
Danhia | bcf0c51ba5 | |
Danhia | 241a3f2bc1 | |
Danhia | cb46708e5c | |
Danhia | 3c166f3834 | |
Danhia | 92a1dca27c | |
Danhia | 96121e401e | |
ix | 97f6b99133 | |
Danhia | c45cfed8d4 | |
ix | 5dc04f888b | |
Danhia | 993e1fa998 | |
Danhia | 9d33985fa4 | |
Clément Hamada | 0cd862734e | |
Clément Hamada | aed129b4b9 | |
Clément Hamada | d1ab64885e | |
Clément Hamada | b2cf802ab5 | |
Clément Hamada | 266e018d83 | |
Danhia | 8fd25d8fa6 | |
ix | 53fe69aa09 | |
Danhia | aadb2ac858 | |
ix | 34b8829797 | |
ix | 619ddf3d07 | |
ix | 5d00c5733f | |
ix | 248cdc54d5 | |
Danhia | f8eef0ef3d | |
Danhia | e806b23812 | |
Danhia | 0fbf7b77c3 | |
Danhia | 985f440ede |
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "src/ctfs/templates/challenges"]
|
||||||
|
path = src/ctfs/templates/challenges
|
||||||
|
url = https://gitea.42ctf.org/42CTF/challenges-descriptions.git
|
74
README.md
74
README.md
|
@ -1,50 +1,34 @@
|
||||||
# 42ctf
|
# 42CTF
|
||||||
CTF by 42 students
|
|
||||||
|
[42CTF](https://www.42ctf.org) is a CTF platform created by School 42 students and open to anyone.
|
||||||
|
|
||||||
|
|
||||||
### Todo
|
### Todo
|
||||||
|
|
||||||
- [x] Serveur SMTP & reset password
|
TODO has been migrated to [issues](https://gitea.42ctf.org/42CTF/website/issues) !
|
||||||
- [x] Clean le repo
|
And hopefully, it is not redirected anymore to `/dev/null`.
|
||||||
- [x] Accès au chall après validation
|
|
||||||
- [x] Section "Intro"
|
|
||||||
- [x] Section Treasure Hunt
|
|
||||||
- [x] Edition de profil
|
|
||||||
- [x] Ajouter de la Doc
|
|
||||||
- [x] Infrastructure de pwn
|
|
||||||
- [x] Organiser une session découverte
|
|
||||||
- [x] Compteur de flags
|
|
||||||
- [x] Graphiques statistiques
|
|
||||||
- [x] Création d'un discord linkable
|
|
||||||
- [ ] Refonte du linkage discord -> 42ctf
|
|
||||||
- [x] Traduction du site
|
|
||||||
- [x] Anglais
|
|
||||||
- [x] Français
|
|
||||||
- [ ] Russe
|
|
||||||
- [ ] Espagnol
|
|
||||||
- [ ] Italien
|
|
||||||
- [ ] OAuth 42
|
|
||||||
- [ ] Feature proposer une solution à un challenge
|
|
||||||
- [ ] Système de badge/succès
|
|
||||||
- [ ] Génération d'une page résumant le profil d'un utilisateur (ex: show resume sur intra.42.fr)
|
|
||||||
|
|
||||||
#### Event feature
|
### How to contribute ?
|
||||||
|
|
||||||
- [X] make relation between user and events
|
First, you need to contact a 42CTF admin to get an account on the 42CTF gitea.
|
||||||
- [X] make scoreboard for events
|
You can contact us on [discord](https://discord.gg/3KDvt6hbWW) or by [email](mailto:42ctf@protonmail.com).
|
||||||
- [X] make access mod for events :
|
You can also fill this [form](https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC) and we'll contact you.
|
||||||
- [X] Sub button for public events
|
Then, once you have a gitea account, you can fork this repository, do some stuff, and open a pull request.
|
||||||
- [X] Access by password
|
|
||||||
- [X] Begin date for display challenges
|
If you want to translate the platform, then have a look at the [wiki](https://gitea.42ctf.org/42CTF/website/wiki).
|
||||||
- [X] Ending date for stop flag submission
|
|
||||||
- [ ] Access by invite link
|
If you want to help with bot development, it has now its own [repository](https://gitea.42ctf.org/42CTF/bot)
|
||||||
- [X] Admin rights
|
|
||||||
- [X] Admin can access to events pages without password
|
### How to set up my dev environment ?
|
||||||
- [X] Admin can subscribe to event without password
|
|
||||||
- [X] process flag submission
|
There is only one file missing on this repository for you to run the server: `local_settings.py`.
|
||||||
- [X] increment user score in Scores model
|
You should create one in the `src` directory, with the following content:
|
||||||
- [X] add filters for admin dashboard
|
```
|
||||||
- [X] add search in fields in admin dashboard
|
DEBUG = True
|
||||||
- [X] display more information in admin dashboard
|
SECRET_KEY = 'what you want'
|
||||||
- [X] Smooth display of events listing
|
```
|
||||||
- [X] Event info page with background and noice display
|
|
||||||
- [ ] Create teams for events
|
When you'll run `python manage.py migrate` then `python manage.py runserver`, an empty database will be automatically created.
|
||||||
|
The `local_settings.py` is in the `.gitignore` and should stay that way, so we don't accidentally overwrite the production file when we deploy.
|
||||||
|
|
||||||
|
To obtain administrator rights you can run `python manage.py createsuperuser`.
|
104
bot.py
104
bot.py
|
@ -1,104 +0,0 @@
|
||||||
import os
|
|
||||||
import discord
|
|
||||||
import discord.utils
|
|
||||||
import urllib.request, json
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
TOKEN = os.getenv('DISCORD_TOKEN')
|
|
||||||
GUILD = '42ctf'
|
|
||||||
|
|
||||||
intents = discord.Intents.all()
|
|
||||||
client = discord.Client(intents=intents)
|
|
||||||
|
|
||||||
db_file = open('members.json', 'r')
|
|
||||||
users = json.load(db_file)
|
|
||||||
db_file.close()
|
|
||||||
|
|
||||||
logging.basicConfig(filename='bot.log', format='%(asctime)s %(message)s', level=logging.INFO)
|
|
||||||
|
|
||||||
guild = ''
|
|
||||||
roles = {}
|
|
||||||
|
|
||||||
def get_rank(token):
|
|
||||||
url = urllib.request.urlopen("https://www.42ctf.org/accounts/rank/" + token)
|
|
||||||
data = json.loads(url.read().decode())
|
|
||||||
rank = data['rank']
|
|
||||||
return rank
|
|
||||||
|
|
||||||
async def watch_roles():
|
|
||||||
global users
|
|
||||||
await client.wait_until_ready() # ensures cache is loaded
|
|
||||||
while not client.is_closed():
|
|
||||||
for member_id, token in users.items():
|
|
||||||
if (token == "0000"):
|
|
||||||
continue
|
|
||||||
member = discord.utils.get(guild.members, id=int(member_id))
|
|
||||||
rank = get_rank(token)
|
|
||||||
if rank == 1 and roles['top1'] not in member.roles:
|
|
||||||
await member.add_roles(roles['top1'])
|
|
||||||
await member.remove_roles(roles['top10'])
|
|
||||||
await member.remove_roles(roles['top50'])
|
|
||||||
elif rank > 1 and rank <= 10 and roles['top10'] not in member.roles:
|
|
||||||
await member.add_roles(roles['top10'])
|
|
||||||
await member.remove_roles(roles['top1'])
|
|
||||||
await member.remove_roles(roles['top50'])
|
|
||||||
elif rank > 10 and rank <= 50 and roles['top50'] not in member.roles:
|
|
||||||
await member.add_roles(roles['top50'])
|
|
||||||
await member.remove_roles(roles['top10'])
|
|
||||||
await member.remove_roles(roles['top1'])
|
|
||||||
elif rank > 50:
|
|
||||||
await member.remove_roles(roles['top1'])
|
|
||||||
await member.remove_roles(roles['top10'])
|
|
||||||
await member.remove_roles(roles['top50'])
|
|
||||||
await asyncio.sleep(60)
|
|
||||||
|
|
||||||
@client.event
|
|
||||||
async def on_ready():
|
|
||||||
global guild, roles
|
|
||||||
guild = discord.utils.get(client.guilds, name=GUILD)
|
|
||||||
roles['top10'] = discord.utils.get(guild.roles, id=801787467064672286)
|
|
||||||
roles['top1'] = discord.utils.get(guild.roles, id=798638767359524875)
|
|
||||||
roles['top50'] = discord.utils.get(guild.roles, id=803729539145924649)
|
|
||||||
|
|
||||||
logging.info('%s is connected to the following guild: %s(id: %d)', client.user, guild.name, guild.id)
|
|
||||||
client.loop.create_task(watch_roles())
|
|
||||||
|
|
||||||
@client.event
|
|
||||||
async def on_message(message):
|
|
||||||
global guild, roles
|
|
||||||
|
|
||||||
if message.author == client.user:
|
|
||||||
return
|
|
||||||
|
|
||||||
if '!connect' in message.content:
|
|
||||||
try:
|
|
||||||
user_token = message.content.split(' ')[1]
|
|
||||||
member = discord.utils.get(guild.members, name=message.author.name)
|
|
||||||
rank = get_rank(user_token)
|
|
||||||
users[str(member.id)] = user_token
|
|
||||||
logging.info("MESSAGE: from %s with token %s", message.author.name, user_token)
|
|
||||||
with open('members.json', 'w') as json_file:
|
|
||||||
json.dump(users, json_file)
|
|
||||||
if rank == 1:
|
|
||||||
await member.add_roles(roles['top1'])
|
|
||||||
response = "Congratulations, you're now Top 1. But for how long ?"
|
|
||||||
|
|
||||||
elif (rank <= 10):
|
|
||||||
await member.add_roles(roles['top10'])
|
|
||||||
response = "You've been granted the Top 10 role. Now, go away and flag !"
|
|
||||||
|
|
||||||
elif rank <= 50:
|
|
||||||
await member.add_roles(roles['top50'])
|
|
||||||
response = "You've been granted the Top 50 role. Now, go away and flag !"
|
|
||||||
|
|
||||||
else:
|
|
||||||
response = "No role for you now, but I'll keep watching you."
|
|
||||||
except IndexError:
|
|
||||||
response = 'usage: !connect 42ctf_token'
|
|
||||||
await message.author.create_dm()
|
|
||||||
await message.author.dm_channel.send(response)
|
|
||||||
|
|
||||||
|
|
||||||
client.run(TOKEN)
|
|
|
@ -1,3 +1,3 @@
|
||||||
Django
|
Django==3.2.11
|
||||||
requests
|
requests==2.27.1
|
||||||
authlib
|
authlib==0.15.5
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from .models import UserProfileInfo
|
from .models import UserProfileInfo
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Campus
|
||||||
|
|
||||||
#admin.site.register(UserProfileInfo)
|
#admin.site.register(UserProfileInfo)
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
@ -7,6 +8,10 @@ from django.contrib import admin
|
||||||
@admin.register(UserProfileInfo)
|
@admin.register(UserProfileInfo)
|
||||||
class userprofile(admin.ModelAdmin):
|
class userprofile(admin.ModelAdmin):
|
||||||
#list display
|
#list display
|
||||||
list_display = ['user', 'score', 'last_submission_date']
|
list_display = ['user', 'score', 'last_submission_date', 'campus']
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['score', 'user__username']
|
search_fields = ['score', 'user__username', 'campus__name']
|
||||||
|
|
||||||
|
@admin.register(Campus)
|
||||||
|
class campus(admin.ModelAdmin):
|
||||||
|
list_display = ['name']
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from accounts import models as acc_models
|
||||||
|
from django.contrib.auth import models as auth_models
|
||||||
|
from django.contrib.auth.models import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Remove all users who never logged in'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
all_users = acc_models.UserProfileInfo.objects.filter(score=0).select_related()
|
||||||
|
to_remove = []
|
||||||
|
for elem in all_users:
|
||||||
|
user = elem.user
|
||||||
|
if user.last_login is None and user.date_joined < timezone.now() - timedelta(hours=72):
|
||||||
|
to_remove.append(user)
|
||||||
|
print("You are going to remove {} users.".format(len(to_remove)))
|
||||||
|
answer = input("Continue ? [y/N] ")
|
||||||
|
|
||||||
|
if answer.lower() in ["y","yes"]:
|
||||||
|
for elem in to_remove:
|
||||||
|
elem.delete()
|
||||||
|
print("Users have been successfully pruned.")
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-03-29 08:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0007_auto_20220123_1704'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='intra42_campus',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='intra42_id',
|
||||||
|
field=models.CharField(blank=True, max_length=20, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-03-29 11:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0008_auto_20220329_1034'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='userprofileinfo',
|
||||||
|
options={'ordering': ['-score', 'last_submission_date', 'user__username', 'intra42_campus'], 'permissions': (('view_info', 'View user info'),), 'verbose_name': 'profile', 'verbose_name_plural': 'profiles'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='intra42_campus_id',
|
||||||
|
field=models.CharField(blank=True, max_length=10, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-05-17 12:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0009_auto_20220329_1339'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Campus',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False, unique=True)),
|
||||||
|
('name', models.CharField(max_length=50)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'campus',
|
||||||
|
'verbose_name_plural': 'campuses',
|
||||||
|
'permissions': (('view_info', 'View user info'),),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='campus',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='accounts.campus'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-05-17 12:54
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from accounts.models import UserProfileInfo
|
||||||
|
from accounts.models import Campus
|
||||||
|
|
||||||
|
def migrate_campus(apps, schema_editor):
|
||||||
|
UserProfileInfo = apps.get_model('accounts', 'UserProfileInfo')
|
||||||
|
Campus = apps.get_model('accounts', 'Campus')
|
||||||
|
for user in UserProfileInfo.objects.all():
|
||||||
|
if user.intra42_campus_id:
|
||||||
|
user.campus, created = Campus.objects.get_or_create(id=user.intra42_campus_id, name=user.intra42_campus)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0010_auto_20220517_1452'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_campus),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-08-01 20:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0011_migration_campus'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='userprofileinfo',
|
||||||
|
options={'ordering': ['-score', 'last_submission_date', 'user__username', 'campus'], 'permissions': (('view_info', 'View user info'),), 'verbose_name': 'profile', 'verbose_name_plural': 'profiles'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='intra42_campus',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='userprofileinfo',
|
||||||
|
name='intra42_campus_id',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-08-18 15:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0012_auto_20220801_2212'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='campus',
|
||||||
|
name='logo',
|
||||||
|
field=models.URLField(default='https://42.fr'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='campus',
|
||||||
|
name='url',
|
||||||
|
field=models.URLField(default='https://42.fr', max_length=100),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-08-18 15:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0013_auto_20220818_1741'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='campus',
|
||||||
|
name='logo',
|
||||||
|
field=models.URLField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='campus',
|
||||||
|
name='url',
|
||||||
|
field=models.URLField(blank=True, max_length=100),
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,15 +12,29 @@ class UserProfileInfo(models.Model):
|
||||||
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
||||||
token = models.CharField(max_length=200, blank=True)
|
token = models.CharField(max_length=200, blank=True)
|
||||||
discord_id = models.CharField(max_length=20, null=True, blank=True, unique=True)
|
discord_id = models.CharField(max_length=20, null=True, blank=True, unique=True)
|
||||||
|
intra42_id = models.CharField(max_length=20, null=True, blank=True, unique=True)
|
||||||
|
campus = models.ForeignKey('Campus', on_delete=models.DO_NOTHING, null=True, blank=True)
|
||||||
member = models.BooleanField(default=False)
|
member = models.BooleanField(default=False)
|
||||||
member_since = models.DateTimeField('Member since', default=timezone.now)
|
member_since = models.DateTimeField('Member since', default=timezone.now)
|
||||||
member_until = models.DateTimeField('Member until', default=timezone.now)
|
member_until = models.DateTimeField('Member until', default=timezone.now)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-score', 'last_submission_date', 'user__username']
|
ordering = ['-score', 'last_submission_date', 'user__username', 'campus']
|
||||||
verbose_name = 'profile'
|
verbose_name = 'profile'
|
||||||
verbose_name_plural = 'profiles'
|
verbose_name_plural = 'profiles'
|
||||||
permissions = (("view_info", "View user info"),)
|
permissions = (("view_info", "View user info"),)
|
||||||
|
|
||||||
|
class Campus(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True, unique=True)
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
url = models.URLField(max_length=100,blank=True)
|
||||||
|
logo = models.URLField(max_length=200,blank=True)
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'campus'
|
||||||
|
verbose_name_plural = 'campuses'
|
||||||
|
permissions = (("view_info", "View user info"),)
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{% trans "Deleted accounts cannot be recovered." %}<br><br>
|
{% trans "Deleted accounts cannot be recovered." %}<br><br>
|
||||||
<div class="col-sm-8 col-md-6 mx-auto">
|
<div class="col-sm-8 col-md-6 mx-auto">
|
||||||
{% if bad_password %}
|
{% if bad_password %}
|
||||||
<span class="message error-msg">{% trans "Password inccorect." %}</span>
|
<span class="message error-msg">{% trans "Password incorrect." %}</span>
|
||||||
{% elif deleted %}
|
{% elif deleted %}
|
||||||
<span class="message success-msg">{% trans "Your account has been deleted." %}</span>
|
<span class="message success-msg">{% trans "Your account has been deleted." %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -2,67 +2,111 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h3>Edit info</h3>
|
<h3>Edit info</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="bloc-body">
|
<div class="bloc-body">
|
||||||
<div class="col-sm-12 col-md-12 mx-auto">
|
<div class="col-sm-12 col-md-12 mx-auto">
|
||||||
{{ u_form.non_field_errors }}
|
{{ u_form.non_field_errors }}
|
||||||
{% if error is not None %}
|
{% if error is not None %}
|
||||||
<span class="message error-msg">{{ error }}</span>
|
<span class="message error-msg">{{ error }}</span>
|
||||||
{% elif success is not None %}
|
{% elif success is not None %}
|
||||||
<span class="message success-msg">{{ success }}</span>
|
<span class="message success-msg">{{ success }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method='POST'>
|
<form method='POST'>
|
||||||
<div class="edit-infos-grp">
|
<div class="edit-infos-grp">
|
||||||
|
{%csrf_token%}
|
||||||
|
<label for="{{ u_form.username.id_for_label }}">{% trans "Username" %} *</label>
|
||||||
|
{{ u_form.username.errors}}
|
||||||
|
{{u_form.username}}
|
||||||
|
</br>
|
||||||
|
<label for="{{ u_form.email.id_for_label }}">{% trans "Email" %} *</label>
|
||||||
|
{{ u_form.email.errors}}
|
||||||
|
{{u_form.email}}
|
||||||
|
</br>
|
||||||
|
</br>
|
||||||
|
<label for="{{ p_form.portfolio_site.id_for_label }}">{% trans "Website" %}</label>
|
||||||
|
{{p_form.portfolio_site}}
|
||||||
|
</br>
|
||||||
|
</br>Token
|
||||||
|
<input type='text' readonly value='{{token}}'>
|
||||||
|
</br>
|
||||||
|
<input class="form-control" type="submit" value="{% trans " Apply" %}">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ctf-block">
|
||||||
|
<div class="ctf-head">
|
||||||
|
<h3>{% trans "Connected accounts" %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="bloc-body">
|
||||||
|
<div class="d-flex">
|
||||||
|
{% if user.userprofileinfo.discord_id|length > 0 %}
|
||||||
|
<form action="{% url 'accounts:connections-disconnect-discord' %}" method='POST'
|
||||||
|
class="form-inline p-2">
|
||||||
{%csrf_token%}
|
{%csrf_token%}
|
||||||
<label for="{{ u_form.username.id_for_label }}">{% trans "Username" %} *</label>
|
<button class="btn btn-dark" type="submit">{% trans "Disconnect Discord" %}</button>
|
||||||
{{ u_form.username.errors}}
|
</form>
|
||||||
{{u_form.username}}
|
{% else %}
|
||||||
</br>
|
<form action="{% url 'accounts:connections-connect-discord' %}" method='POST'
|
||||||
<label for="{{ u_form.email.id_for_label }}">{% trans "Email" %} *</label>
|
class="form-inline p-2">
|
||||||
{{ u_form.email.errors}}
|
{%csrf_token%}
|
||||||
{{u_form.email}}
|
<button class="btn btn-dark" type="submit">{% trans "Connect Discord" %}</button>
|
||||||
</br>
|
</form>
|
||||||
</br>
|
{% endif %}
|
||||||
<label for="{{ p_form.portfolio_site.id_for_label }}">{% trans "Website" %}</label>
|
</div>
|
||||||
{{p_form.portfolio_site}}
|
<div class="d-flex">
|
||||||
</br>
|
{% if user.userprofileinfo.intra42_id|length > 0 %}
|
||||||
</br>Token
|
<form action="{% url 'accounts:connections-disconnect-intra42' %}" method='POST'
|
||||||
<input type='text' readonly value='{{token}}'>
|
class="form-inline p-2">
|
||||||
</br>
|
{%csrf_token%}
|
||||||
<input class="form-control" type="submit" value="{% trans "Apply" %}">
|
<button class="btn btn-dark" type="submit">{% trans "Disconnect 42" %}</button>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
{% else %}
|
||||||
|
<form action="{% url 'accounts:connections-connect-intra42' %}" method='POST'
|
||||||
|
class="form-inline p-2">
|
||||||
|
{%csrf_token%}
|
||||||
|
<button class="btn btn-dark" type="submit">{% trans "Connect 42" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
||||||
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
<ul class="list-group">
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">{{ user.username }}</li>
|
<li class="list-group-item">{{ user.username }}</li>
|
||||||
<li class="list-group-item">{% trans "Score" %} : {{ user.userprofileinfo.score }}</li>
|
<li class="list-group-item">{% trans "Score" %} : {{ user.userprofileinfo.score }}</li>
|
||||||
{% if user.userprofileinfo.portfolio_site %}
|
{% if user.userprofileinfo.portfolio_site %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="{{ user.userprofileinfo.portfolio_site }}" target="_blank">
|
<a href="{{ user.userprofileinfo.portfolio_site }}" target="_blank">
|
||||||
{{ user.userprofileinfo.portfolio_site }}
|
{{ user.userprofileinfo.portfolio_site }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"Y-m-d" }}</li>
|
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"Y-m-d" }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<form method='GET' action="{% url 'accounts:delete_account' %}">
|
<form method='GET' action="{% url 'accounts:profile' user %}">
|
||||||
{%csrf_token%}
|
<li class="list-group-item">
|
||||||
<li class="list-group-item">
|
<input class="form-control" type="submit" value="{% trans " View my profile" %}">
|
||||||
<input class="form-control" type="submit" value="{% trans "Delete my account" %}">
|
</li>
|
||||||
</li>
|
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<ul class="list-group">
|
||||||
|
<form method='GET' action="{% url 'accounts:delete_account' %}">
|
||||||
|
{%csrf_token%}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<input class="form-control" type="submit" value="{% trans " Delete my account" %}">
|
||||||
|
</li>
|
||||||
|
</form>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,9 @@
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if member %}
|
{% if member %}
|
||||||
<li class="list-group-item is-member">{% trans "Status: Member" %}</li>
|
<li class="list-group-item">Status: <a class="{{ is_member }}" href="{% url 'resources:becomeMember' %}">{% trans "Member" %}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="list-group-item">{% trans "Status: Visitor" %}</li>
|
<li class="list-group-item">Status: {% trans " Visitor" %}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li>
|
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -75,13 +75,26 @@
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">{% trans "Challenges created" %}</li>
|
||||||
|
{% if created %}
|
||||||
|
{% for creat in created %}
|
||||||
|
<li class="list-group-item"><a href="{% url 'ctf' cat_slug=creat.category.slug ctf_slug=creat.slug %}">{{ creat.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="list-group-item">{% trans "It seems that this user has not created any challenge yet..." %}</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://code.highcharts.com/highcharts.src.js"></script>
|
<script src="https://code.highcharts.com/highcharts.src.js"></script>
|
||||||
<script>
|
<script>
|
||||||
Highcharts.theme={colors:["#2b908f","#90ee7e","#f45b5b","#7798BF","#aaeeee","#ff0066","#eeaaee","#55BF3B","#DF5353","#7798BF","#aaeeee"],chart:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:1,y2:1},stops:[[0,"#1D1D1D"],[1,"#1D1D1D"]]},style:{fontFamily:"'Unica One', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",textTransform:"uppercase",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3",textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#F0F0F3",style:{fontSize:"13px"}},marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{backgroundColor:"#1D1D1D",itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"},title:{style:{color:"#C0C0C0"}}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}},rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}},scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"}};
|
Highcharts.theme={colors:["#2b908f","#90ee7e","#f45b5b","#7798BF","#aaeeee","#ff0066","#eeaaee","#55BF3B","#DF5353","#7798BF","#aaeeee"],chart:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:1,y2:1},stops:[[0,"#1D1D1D"],[1,"#1D1D1D"]]},style:{fontFamily:"'Unica One', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",textTransform:"uppercase",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3",textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#F0F0F3",style:{fontSize:"13px"}},marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{backgroundColor:"#1D1D1D",itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"},title:{style:{color:"#C0C0C0"}}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}},rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}},scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"}};
|
||||||
|
|
||||||
Highcharts.setOptions(Highcharts.theme);
|
Highcharts.setOptions(Highcharts.theme);
|
||||||
|
|
||||||
Highcharts.chart('time-chart', {
|
Highcharts.chart('time-chart', {
|
||||||
|
@ -122,7 +135,7 @@
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Total',
|
name: 'Total',
|
||||||
data: {{ solved|safe }}
|
data: {{ solved|safe }}
|
||||||
},
|
},
|
||||||
{% for cat in cats %}
|
{% for cat in cats %}
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,42 +2,40 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h3>Register</h3>
|
<h3>Register</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="ctf-body sign-body">
|
<div class="ctf-body sign-body">
|
||||||
<div class="col-sm-8 col-md-6 mx-auto">
|
<div class="col-sm-8 col-md-6 mx-auto">
|
||||||
{% if registered %}
|
{% if registered %}
|
||||||
<h1>{% trans "Welcome !" %}</h1>
|
<h1>{% trans "Welcome !" %}</h1>
|
||||||
<span class="message success-msg">{% trans "Your account has been created." %}</span>
|
<span class="message success-msg">{% trans "Your account has been created." %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if registered_failed %}
|
{% if registered_failed %}
|
||||||
<span class="message error-msg">{{ registered_failed }}</span>
|
<span class="message error-msg">{{ registered_failed }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form enctype="multipart/form-data" method="POST">
|
<form enctype="multipart/form-data" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control" type="text" name="username" placeholder="{% trans "Username" %} *" maxlength="150" required="" id="id_username"></br>
|
<input class="form-control" type="text" name="username" placeholder="{% trans "Username" %} *" maxlength="150" required="" id="id_username" value="{{ old_username }}"></br>
|
||||||
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
||||||
<input class="form-control" type="email" name="email" placeholder="pleasedontgivemy@private.infos*" required="" maxlength="254" id="id_email"></br>
|
<input class="form-control" type="email" name="email" placeholder="pleasedontgivemy@private.infos*" required="" maxlength="254" id="id_email" value="{{ old_email }}"></br>
|
||||||
<input class="form-control" type="url" name="portfolio_site" placeholder="{% trans "Personal website" %}"maxlength="200" id="id_portfolio_site"></br>
|
<input class="form-control" type="url" name="portfolio_site" placeholder="{% trans "Personal website" %}"maxlength="200" id="id_portfolio_site" value="{{ old_website }}"></br>
|
||||||
<input type="submit" name="" class="form-control" value="{% trans "Register" %}">
|
<input type="submit" name="" class="form-control" value="{% trans "Register" %}">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
<div class="col-sm-12 col-md-3 right-sidebar">
|
||||||
<div class="col-sm-12 col-md-3 right-sidebar">
|
<ul class="list-group">
|
||||||
<ul class="list-group">
|
<a href="/accounts/signup" class="list-group-item">{% trans "Sign up" %}</a>
|
||||||
<a href="/accounts/signup" class="list-group-item">{% trans "Sign up" %}</a>
|
<a href="/accounts/signin" class="list-group-item">{% trans "Login" %}</a>
|
||||||
<a href="/accounts/signin" class="list-group-item">{% trans "Login" %}</a>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,11 @@ urlpatterns = [
|
||||||
path('edit/', views.edit, name='edit'),
|
path('edit/', views.edit, name='edit'),
|
||||||
path('logout/', views.out, name='out'),
|
path('logout/', views.out, name='out'),
|
||||||
path('rank/<str:token>', views.rank, name='rank'),
|
path('rank/<str:token>', views.rank, name='rank'),
|
||||||
path('connections/connect/discord', views.connection.connect, name='connections-connect-discord'),
|
path('connections/connect/discord', views.connection.connect_discord, name='connections-connect-discord'),
|
||||||
path('connections/connect/discord/authorize', views.connection.authorize, name='connections-connect-discord-authorize'),
|
path('connections/connect/discord/authorize', views.connection.authorize_discord, name='connections-connect-discord-authorize'),
|
||||||
path('connections/disconnect/discord', views.connection.disconnect, name='connections-disconnect-discord'),
|
path('connections/disconnect/discord', views.connection.disconnect_discord, name='connections-disconnect-discord'),
|
||||||
|
path('connections/connect/intra42', views.connection.connect_intra42, name='connections-connect-intra42'),
|
||||||
|
path('connections/connect/intra42/authorize', views.connection.authorize_intra42, name='connections-connect-intra42-authorize'),
|
||||||
|
path('connections/disconnect/intra42', views.connection.disconnect_intra42, name='connections-disconnect-intra42'),
|
||||||
path('delete_account/', views.delete_account, name='delete_account'),
|
path('delete_account/', views.delete_account, name='delete_account'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,47 +4,106 @@ from django.views.decorators.http import require_POST
|
||||||
from django.views.defaults import bad_request
|
from django.views.defaults import bad_request
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from accounts.models import Campus
|
||||||
|
from django.db import IntegrityError
|
||||||
import os
|
import os
|
||||||
|
|
||||||
oauth = OAuth()
|
oauth = OAuth()
|
||||||
|
|
||||||
oauth.register(
|
oauth.register(
|
||||||
name='discord',
|
name='discord',
|
||||||
client_id=os.getenv('OAUTH2_DISCORD_CLIENT_ID'),
|
client_id=os.getenv('OAUTH2_DISCORD_CLIENT_ID'),
|
||||||
client_secret=os.getenv('OAUTH2_DISCORD_CLIENT_SECRET'),
|
client_secret=os.getenv('OAUTH2_DISCORD_CLIENT_SECRET'),
|
||||||
access_token_url='https://discord.com/api/oauth2/token',
|
access_token_url='https://discord.com/api/oauth2/token',
|
||||||
authorize_url='https://discord.com/api/oauth2/authorize',
|
authorize_url='https://discord.com/api/oauth2/authorize',
|
||||||
client_kwargs={'scope': 'identify'},
|
client_kwargs={'scope': 'identify'},
|
||||||
api_base_url='https://discord.com/api/'
|
api_base_url='https://discord.com/api/'
|
||||||
|
)
|
||||||
|
|
||||||
|
oauth.register(
|
||||||
|
name='intra42',
|
||||||
|
client_id=os.getenv('OAUTH2_INTRA42_CLIENT_ID'),
|
||||||
|
client_secret=os.getenv('OAUTH2_INTRA42_CLIENT_SECRET'),
|
||||||
|
access_token_url='https://api.intra.42.fr/oauth/token',
|
||||||
|
authorize_url='https://api.intra.42.fr/oauth/authorize',
|
||||||
|
api_base_url='https://api.intra.42.fr/'
|
||||||
)
|
)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def connect(request):
|
def connect_intra42(request):
|
||||||
if request.user.userprofileinfo.discord_id:
|
if request.user.userprofileinfo.intra42_id:
|
||||||
return bad_request(request, "Already connected")
|
return bad_request(request, "Already connected")
|
||||||
redirect_uri = reverse('accounts:connections-connect-discord-authorize')
|
site = Site.objects.get_current()
|
||||||
redirect_uri = request.build_absolute_uri(redirect_uri)
|
redirect_uri = reverse('accounts:connections-connect-intra42-authorize')
|
||||||
print(redirect_uri)
|
redirect_uri = "https://" + site.domain + redirect_uri[3:] # remove language code
|
||||||
return oauth.discord.authorize_redirect(request, redirect_uri)
|
return oauth.intra42.authorize_redirect(request, redirect_uri)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def authorize(request):
|
def authorize_intra42(request):
|
||||||
if request.user.userprofileinfo.discord_id:
|
if request.user.userprofileinfo.intra42_id:
|
||||||
return bad_request(request, "Already connected")
|
return bad_request(request, "Already connected")
|
||||||
token = oauth.discord.authorize_access_token(request)
|
try:
|
||||||
response = oauth.discord.get('users/@me', token=token)
|
token = oauth.intra42.authorize_access_token(request)
|
||||||
response = response.json()
|
except:
|
||||||
discord_id = response['id']
|
return redirect('accounts:edit')
|
||||||
request.user.userprofileinfo.discord_id = discord_id
|
response = oauth.intra42.get('v2/me', token=token)
|
||||||
request.user.userprofileinfo.save()
|
response = response.json()
|
||||||
return redirect('accounts:edit')
|
intra42_id = response['id']
|
||||||
|
intra42_campus = response['campus'][0]['name']
|
||||||
|
intra42_campus_id = response['campus'][0]['id']
|
||||||
|
request.user.userprofileinfo.intra42_id = intra42_id
|
||||||
|
request.user.userprofileinfo.campus, created = Campus.objects.get_or_create(id=intra42_campus_id, name=intra42_campus)
|
||||||
|
try:
|
||||||
|
request.user.userprofileinfo.save()
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
except IntegrityError:
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def disconnect(request):
|
def disconnect_intra42(request):
|
||||||
if not request.user.userprofileinfo.discord_id:
|
if not request.user.userprofileinfo.intra42_id:
|
||||||
return bad_request(request, "Already disconnected")
|
return bad_request(request, "Already disconnected")
|
||||||
request.user.userprofileinfo.discord_id = None
|
request.user.userprofileinfo.intra42_id = None
|
||||||
request.user.userprofileinfo.save()
|
request.user.userprofileinfo.intra42_campus = None
|
||||||
return redirect('accounts:edit')
|
request.user.userprofileinfo.intra42_campus_id = None
|
||||||
|
request.user.userprofileinfo.campus = None
|
||||||
|
request.user.userprofileinfo.save()
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def connect_discord(request):
|
||||||
|
if request.user.userprofileinfo.discord_id:
|
||||||
|
return bad_request(request, "Already connected")
|
||||||
|
site = Site.objects.get_current()
|
||||||
|
redirect_uri = reverse('accounts:connections-connect-discord-authorize')
|
||||||
|
redirect_uri = "https://" + site.domain + redirect_uri[3:] # remove language code
|
||||||
|
return oauth.discord.authorize_redirect(request, redirect_uri)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def authorize_discord(request):
|
||||||
|
if request.user.userprofileinfo.discord_id:
|
||||||
|
return bad_request(request, "Already connected")
|
||||||
|
try:
|
||||||
|
token = oauth.discord.authorize_access_token(request)
|
||||||
|
except:
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
response = oauth.discord.get('users/@me', token=token)
|
||||||
|
response = response.json()
|
||||||
|
discord_id = response['id']
|
||||||
|
request.user.userprofileinfo.discord_id = discord_id
|
||||||
|
request.user.userprofileinfo.save()
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def disconnect_discord(request):
|
||||||
|
if not request.user.userprofileinfo.discord_id:
|
||||||
|
return bad_request(request, "Already disconnected")
|
||||||
|
request.user.userprofileinfo.discord_id = None
|
||||||
|
request.user.userprofileinfo.save()
|
||||||
|
return redirect('accounts:edit')
|
||||||
|
|
|
@ -43,32 +43,69 @@ def signup(request):
|
||||||
user_form = UserForm()
|
user_form = UserForm()
|
||||||
profile_form = UserProfileInfoForm()
|
profile_form = UserProfileInfoForm()
|
||||||
registered = False
|
registered = False
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
pass1 = request.POST.get('password')
|
username = request.POST.get('username')
|
||||||
if len(pass1) < 8:
|
passwd = request.POST.get('password')
|
||||||
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':"The new password must be at least %d characters long." % 8})
|
email = request.POST.get('email')
|
||||||
first_isalpha = pass1[0].isalpha()
|
website = request.POST.get('portfolio_site')
|
||||||
if not any(c.isdigit() for c in pass1) or not any(c.isalpha() for c in pass1):
|
|
||||||
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("The password must contain at least one letter and at least one digit or punctuation character.")})
|
if len(passwd) < 8:
|
||||||
|
return render(request, 'accounts/register.html', {
|
||||||
|
'user_form': user_form,
|
||||||
|
'profile_form': profile_form,
|
||||||
|
'registered_failed': _("The password must be at least 8 characters long."),
|
||||||
|
'old_username': username,
|
||||||
|
'old_email': email,
|
||||||
|
'old_website': website
|
||||||
|
})
|
||||||
|
|
||||||
|
if not any(c.isdigit() for c in passwd) or not any(c.isalpha() for c in passwd):
|
||||||
|
return render(request, 'accounts/register.html', {
|
||||||
|
'user_form': user_form,
|
||||||
|
'profile_form': profile_form,
|
||||||
|
'registered_failed': _("The password must contain at least one letter and at least one digit or punctuation character."),
|
||||||
|
'old_username': username,
|
||||||
|
'old_email': email,
|
||||||
|
'old_website': website
|
||||||
|
})
|
||||||
|
|
||||||
if User.objects.filter(email=request.POST.get('email')).exists():
|
if User.objects.filter(email=request.POST.get('email')).exists():
|
||||||
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that email already exists.")})
|
return render(request, 'accounts/register.html', {
|
||||||
|
'user_form': user_form,
|
||||||
|
'profile_form': profile_form,
|
||||||
|
'registered_failed': _("A user with that email already exists."),
|
||||||
|
'old_username': username,
|
||||||
|
'old_website': website
|
||||||
|
})
|
||||||
|
|
||||||
user_form = UserForm(data=request.POST)
|
user_form = UserForm(data=request.POST)
|
||||||
profile_form = UserProfileInfoForm(data=request.POST)
|
profile_form = UserProfileInfoForm(data=request.POST)
|
||||||
if user_form.is_valid() and profile_form.is_valid():
|
if user_form.is_valid() and profile_form.is_valid():
|
||||||
user = user_form.save()
|
user = user_form.save()
|
||||||
user.set_password(user.password)
|
user.set_password(user.password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
profile = profile_form.save(commit=False)
|
profile = profile_form.save(commit=False)
|
||||||
profile.user = user
|
profile.user = user
|
||||||
profile.token = token_hex(16)
|
profile.token = token_hex(16)
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
registered = True
|
registered = True
|
||||||
else:
|
else:
|
||||||
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that username already exists.")})
|
return render(request, 'accounts/register.html', {
|
||||||
return render(request,'accounts/register.html',
|
'user_form': user_form,
|
||||||
{'user_form':user_form,
|
'profile_form': profile_form,
|
||||||
'profile_form':profile_form,
|
'registered_failed': _("A user with that username already exists."),
|
||||||
'registered':registered})
|
'old_email': email,
|
||||||
|
'old_website': website
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(request, 'accounts/register.html', {
|
||||||
|
'user_form': user_form,
|
||||||
|
'profile_form': profile_form,
|
||||||
|
'registered': registered
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(reverse('home'))
|
return HttpResponseRedirect(reverse('home'))
|
||||||
|
|
||||||
|
@ -124,16 +161,18 @@ def profile(request, user_name):
|
||||||
member = True
|
member = True
|
||||||
else:
|
else:
|
||||||
member = False
|
member = False
|
||||||
cats = Category.objects.all()
|
all_cats = Category.objects.all()
|
||||||
|
cats = [cat for cat in all_cats if CTF.objects.filter(category__name=cat.name, event=None, disabled=False)]
|
||||||
pointDatas = {}
|
pointDatas = {}
|
||||||
|
|
||||||
for cat in cats:
|
for cat in cats:
|
||||||
# prepare categories
|
# prepare categories
|
||||||
solved_count = CTF_flags.objects.filter(user=user_obj, ctf__event=None , ctf__category__name=cat.name).count()
|
solved = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name, ctf__event=None, ctf__disabled=False).order_by('flag_date')
|
||||||
max_count = CTF.objects.filter(category__name=cat.name, event=None).count()
|
max_count = CTF.objects.filter(category__name=cat.name, event=None, disabled=False).count()
|
||||||
# get datas
|
# get datas
|
||||||
somme = 0
|
somme = 0
|
||||||
solved = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name, ctf__event=None).order_by('flag_date')
|
solved_count = len(solved)
|
||||||
|
|
||||||
pointDatas[cat.name] = []
|
pointDatas[cat.name] = []
|
||||||
pointDatas[cat.name].append([user_obj.date_joined.timestamp() * 1000, 0])
|
pointDatas[cat.name].append([user_obj.date_joined.timestamp() * 1000, 0])
|
||||||
percent = (solved_count / max_count) * 100
|
percent = (solved_count / max_count) * 100
|
||||||
|
@ -142,27 +181,16 @@ def profile(request, user_name):
|
||||||
somme += flag.ctf.points
|
somme += flag.ctf.points
|
||||||
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
|
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
|
||||||
|
|
||||||
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=None).order_by('-flag_date')
|
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=None, ctf__disabled=False).order_by('-flag_date')
|
||||||
solved = []
|
solved = []
|
||||||
somme = 0
|
somme = 0
|
||||||
solved.append([user_obj.date_joined.timestamp() * 1000, 0])
|
solved.append([user_obj.date_joined.timestamp() * 1000, 0])
|
||||||
for s in solves.reverse():
|
for s in solves.reverse():
|
||||||
somme += s.ctf.points
|
somme += s.ctf.points
|
||||||
solved.append([s.flag_date.timestamp() * 1000,somme])
|
solved.append([s.flag_date.timestamp() * 1000,somme])
|
||||||
|
created = CTF.objects.filter(author=user_obj, event=None)
|
||||||
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
|
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
|
||||||
'rank': rank, 'score' : somme, 'member' : member})
|
'rank': rank, 'score' : somme, 'member' : member, 'cats':cats, 'created':created})
|
||||||
|
|
||||||
def rank(request, token):
|
|
||||||
all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username')
|
|
||||||
|
|
||||||
rank = 1
|
|
||||||
for elem in all_users:
|
|
||||||
if elem.token == token:
|
|
||||||
break
|
|
||||||
rank += 1
|
|
||||||
data = {"rank": rank}
|
|
||||||
return JsonResponse(data)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete_account(request):
|
def delete_account(request):
|
||||||
|
@ -179,4 +207,15 @@ def delete_account(request):
|
||||||
return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': True})
|
return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': True})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': False} )
|
return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': False} )
|
||||||
|
|
||||||
|
def rank(request, token):
|
||||||
|
all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username')
|
||||||
|
|
||||||
|
rank = 1
|
||||||
|
for elem in all_users:
|
||||||
|
if elem.token == token:
|
||||||
|
break
|
||||||
|
rank += 1
|
||||||
|
data = {"rank": rank}
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'api'
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('bot/discord', views.bot_discord_rank, name='bot_discord_rank'), # legacy, to remove when new bot is deployed
|
||||||
|
path('bot/discord/rank', views.bot_discord_rank, name='bot_discord_rank'), # use this
|
||||||
|
path('bot/discord/campus', views.bot_discord_campus, name='bot_discord_campus'),
|
||||||
|
path('events/<str:event_slug>', views.events_data, name='events_data'),
|
||||||
|
]
|
|
@ -0,0 +1,72 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
from accounts.models import UserProfileInfo
|
||||||
|
from django.http import JsonResponse
|
||||||
|
import os
|
||||||
|
from events.models import Event, Team, EventPlayer
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
|
||||||
|
def bot_discord_rank(request):
|
||||||
|
if request.method != 'GET':
|
||||||
|
return JsonResponse({'error':'bad request'})
|
||||||
|
|
||||||
|
token = request.GET.get('token')
|
||||||
|
auth_token = os.getenv('BOT_TOKEN')
|
||||||
|
|
||||||
|
if (token != auth_token or not auth_token):
|
||||||
|
return JsonResponse({'error':'not authorized'})
|
||||||
|
|
||||||
|
all_users = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')
|
||||||
|
data = {}
|
||||||
|
rank = 1
|
||||||
|
for user in all_users:
|
||||||
|
if user.discord_id:
|
||||||
|
data[user.discord_id] = rank
|
||||||
|
rank += 1
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
def bot_discord_campus(request):
|
||||||
|
if request.method != 'GET':
|
||||||
|
return JsonResponse({'error':'bad request'})
|
||||||
|
|
||||||
|
token = request.GET.get('token')
|
||||||
|
auth_token = os.getenv('BOT_TOKEN')
|
||||||
|
|
||||||
|
if (token != auth_token or not auth_token):
|
||||||
|
return JsonResponse({'error':'not authorized'})
|
||||||
|
|
||||||
|
all_users = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')
|
||||||
|
data = {}
|
||||||
|
for user in all_users:
|
||||||
|
if user.campus and user.discord_id:
|
||||||
|
data[user.discord_id] = user.campus.name
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
def events_data(request, event_slug):
|
||||||
|
if request.method != 'GET':
|
||||||
|
return JsonResponse({'error':'bad request'})
|
||||||
|
|
||||||
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
|
|
||||||
|
if event_info.password and request.GET.get('password') != event_info.password:
|
||||||
|
return JsonResponse({'error':'not authorized'})
|
||||||
|
|
||||||
|
players = EventPlayer.objects.filter(event=event_info)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if event_info.team_size > 1:
|
||||||
|
for player in players:
|
||||||
|
if not player.team:
|
||||||
|
continue
|
||||||
|
if not player.team.name in data:
|
||||||
|
data[player.team.name] = []
|
||||||
|
data[player.team.name].append({"name": player.user.username, "score": player.score})
|
||||||
|
else:
|
||||||
|
for player in players:
|
||||||
|
data[player.user.username] = player.score
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
|
@ -2,8 +2,6 @@ from django.contrib import admin
|
||||||
from .models import Category, CTF, CTF_flags
|
from .models import Category, CTF, CTF_flags
|
||||||
|
|
||||||
admin.site.register(Category)
|
admin.site.register(Category)
|
||||||
#admin.site.register(CTF)
|
|
||||||
#admin.site.register(CTF_flags)
|
|
||||||
|
|
||||||
@admin.register(CTF_flags)
|
@admin.register(CTF_flags)
|
||||||
class ctf_flags(admin.ModelAdmin):
|
class ctf_flags(admin.ModelAdmin):
|
||||||
|
@ -14,12 +12,61 @@ class ctf_flags(admin.ModelAdmin):
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['ctf__category__name', 'ctf__name', 'user__username']
|
search_fields = ['ctf__category__name', 'ctf__name', 'user__username']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
groups = list(request.user.groups.values_list('name', flat=True))
|
||||||
|
return qs.filter(event__name__in=groups)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_delete_permission(request, obj)
|
||||||
|
|
||||||
@admin.register(CTF)
|
@admin.register(CTF)
|
||||||
class ctf(admin.ModelAdmin):
|
class ctf(admin.ModelAdmin):
|
||||||
#list display
|
#list display
|
||||||
list_display = ['name', 'event', 'category']
|
list_display = ['name', 'event', 'category', 'points']
|
||||||
#list Filter
|
#list Filter
|
||||||
list_filter = ('category', 'event')
|
list_filter = ('category', 'event')
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['category__name', 'name', 'author__username']
|
search_fields = ['category__name', 'name', 'author__username']
|
||||||
# Register your models here.
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
groups = list(request.user.groups.values_list('name', flat=True))
|
||||||
|
return qs.filter(event__name__in=groups)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from accounts.models import UserProfileInfo
|
||||||
|
from ctfs.models import CTF_flags, CTF
|
||||||
|
from math import log
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Actualize challenges points based on number of solves'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
challenges = CTF.objects.filter(event=None, disabled=False).exclude(category__name="-Intro-")
|
||||||
|
|
||||||
|
for ctf in challenges:
|
||||||
|
solves = CTF_flags.objects.filter(ctf=ctf)
|
||||||
|
nb_solves = len(solves)
|
||||||
|
|
||||||
|
if nb_solves > 0:
|
||||||
|
new_points = max(200 - int(log(nb_solves)*8.5)*5, 5)
|
||||||
|
else:
|
||||||
|
new_points = 200
|
||||||
|
|
||||||
|
if new_points != ctf.points:
|
||||||
|
diff = ctf.points - new_points
|
||||||
|
ctf.points = new_points
|
||||||
|
ctf.save()
|
||||||
|
for s in solves:
|
||||||
|
s.user.userprofileinfo.score -= diff
|
||||||
|
s.user.userprofileinfo.save()
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1.5 on 2022-02-03 17:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ctfs', '0006_alter_ctf_event'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ctf',
|
||||||
|
name='disabled',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-02-15 16:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ctfs', '0007_ctf_disabled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ctf',
|
||||||
|
name='description_de',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ctf',
|
||||||
|
name='description_ru',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ctf',
|
||||||
|
name='port',
|
||||||
|
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.11 on 2023-09-17 17:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ctfs', '0008_auto_20220215_1713'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ctf_flags',
|
||||||
|
name='bonus',
|
||||||
|
field=models.PositiveSmallIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,12 +12,12 @@ class Category(models.Model):
|
||||||
class CTF(models.Model):
|
class CTF(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
flag = models.CharField(max_length=100)
|
flag = models.CharField(max_length=100)
|
||||||
|
disabled = models.BooleanField(default=False)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
description_en = models.TextField(blank=True)
|
description_en = models.TextField(blank=True)
|
||||||
description_ru = models.TextField(blank=True)
|
|
||||||
description_de = models.TextField(blank=True)
|
|
||||||
file = models.FileField(blank=True, upload_to='challenges')
|
file = models.FileField(blank=True, upload_to='challenges')
|
||||||
ctf_url = models.URLField(blank=True)
|
ctf_url = models.URLField(blank=True)
|
||||||
|
port = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||||
event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
points = models.PositiveSmallIntegerField()
|
points = models.PositiveSmallIntegerField()
|
||||||
slug = models.SlugField(max_length=55)
|
slug = models.SlugField(max_length=55)
|
||||||
|
@ -45,6 +45,7 @@ class CTF_flags(models.Model):
|
||||||
user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
|
||||||
ctf = models.ForeignKey(CTF, unique=False, on_delete=models.CASCADE)
|
ctf = models.ForeignKey(CTF, unique=False, on_delete=models.CASCADE)
|
||||||
flag_date = models.DateTimeField('Flag date')
|
flag_date = models.DateTimeField('Flag date')
|
||||||
|
bonus = models.PositiveSmallIntegerField(default=0)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-flag_date']
|
ordering = ['-flag_date']
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
from django.contrib import sitemaps
|
||||||
|
from django.urls import reverse
|
||||||
|
from .models import Category, CTF
|
||||||
|
from .views import category, ctf
|
||||||
|
|
||||||
|
class CategorySitemap(sitemaps.Sitemap):
|
||||||
|
changefreq = "weekly"
|
||||||
|
priority = 0.7
|
||||||
|
i18n = True
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return Category.objects.all()
|
||||||
|
|
||||||
|
def location(self, obj):
|
||||||
|
return reverse(category, kwargs={'cat_slug': obj.slug})
|
||||||
|
|
||||||
|
class CTFSitemap(sitemaps.Sitemap):
|
||||||
|
changefreq = "weekly"
|
||||||
|
priority = 0.7
|
||||||
|
i18n = True
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return CTF.objects.all()
|
||||||
|
|
||||||
|
def location(self, obj):
|
||||||
|
return reverse(ctf, kwargs={'cat_slug': obj.category.slug, 'ctf_slug': obj.slug})
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5c7b5995fe12c0ed1bb10f97e56ec89377c98b54
|
|
@ -2,11 +2,13 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load is_member %}
|
{% load is_member %}
|
||||||
|
{% load get_chall %}
|
||||||
|
{% get_current_language as lang %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h3>{{ ctf.name }}</h3>
|
<h1>{{ ctf.name }}</h1>
|
||||||
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
|
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
|
||||||
</div>
|
</div>
|
||||||
{% if date < ctf.pub_date %}
|
{% if date < ctf.pub_date %}
|
||||||
|
@ -15,11 +17,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="ctf-body">
|
<div class="ctf-body">
|
||||||
{% if description %}
|
{% get_chall_by_lang ctf lang as content %}
|
||||||
{{ description|safe }}
|
{{ content | safe }}
|
||||||
{% else %}
|
<!-- {% if ctf.port %}
|
||||||
{% trans "No translation available. Please try another language (English or French)." %}
|
<b>nc challenges.42ctf.org {{ ctf.port }}</b>
|
||||||
{% endif %}
|
{% endif %} -->
|
||||||
</div>
|
</div>
|
||||||
<div class="ctf-footer">
|
<div class="ctf-footer">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-12 ctf-head">
|
||||||
|
<h1>{{ cat.name }}</h1>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12 col-md-9 news-card">
|
<div class="col-sm-12 col-md-9 news-card">
|
||||||
<h3>{{ cat.name }}</h3>
|
|
||||||
{% if ctfs %}
|
{% if ctfs %}
|
||||||
<table class="table table-striped table-dark">
|
<table class="table table-striped table-dark">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def get_chall_by_lang(chall, lang):
|
||||||
|
filepath = "ctfs/templates/challenges/"+ lang + "/" + chall.slug + ".html"
|
||||||
|
try:
|
||||||
|
with open(filepath) as fp:
|
||||||
|
return fp.read()
|
||||||
|
except:
|
||||||
|
return chall.description_en
|
|
@ -1,7 +1,15 @@
|
||||||
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from .sitemaps import CategorySitemap, CTFSitemap
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
sitemaps = {
|
||||||
|
'categories': CategorySitemap(),
|
||||||
|
'challenges': CTFSitemap(),
|
||||||
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('<str:cat_slug>/', views.category, name='category'),
|
path('<str:cat_slug>/', views.category, name='category'),
|
||||||
path('<str:cat_slug>/<str:ctf_slug>', views.ctf, name='ctf')
|
path('<str:cat_slug>/<str:ctf_slug>', views.ctf, name='ctf'),
|
||||||
|
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,33 +5,38 @@ from .models import Category, CTF, CTF_flags
|
||||||
from .forms import submit_flag
|
from .forms import submit_flag
|
||||||
from accounts.models import UserProfileInfo
|
from accounts.models import UserProfileInfo
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
from math import log
|
||||||
|
from accounts.models import UserProfileInfo
|
||||||
|
|
||||||
def get_description_by_lang(ctf):
|
def actualize_points(ctf):
|
||||||
lang = get_language()
|
if ctf.category.name == "-Intro-":
|
||||||
ret = None
|
return
|
||||||
if lang == "fr":
|
solves = CTF_flags.objects.filter(ctf=ctf)
|
||||||
ret = ctf.description
|
nb_solves = len(solves)
|
||||||
elif lang == "en":
|
|
||||||
ret = ctf.description_en
|
new_points = max(200 - int(log(nb_solves)*8.5)*5, 5)
|
||||||
elif lang == "de":
|
|
||||||
ret = ctf.description_de
|
if new_points != ctf.points:
|
||||||
elif lang == "ru":
|
diff = ctf.points - new_points
|
||||||
ret = ctf.description_ru
|
ctf.points = new_points
|
||||||
return ret
|
ctf.save()
|
||||||
|
for s in solves:
|
||||||
|
s.user.userprofileinfo.score -= diff
|
||||||
|
s.user.userprofileinfo.save()
|
||||||
|
|
||||||
|
|
||||||
def category(request, cat_slug):
|
def category(request, cat_slug):
|
||||||
cat = get_object_or_404(Category, slug=cat_slug)
|
cat = get_object_or_404(Category, slug=cat_slug)
|
||||||
ctfs = CTF.objects.filter(category=cat, event=None).order_by('points')
|
ctfs = CTF.objects.filter(category=cat, event=None, disabled=False).order_by('points')
|
||||||
for ex in ctfs:
|
for ex in ctfs:
|
||||||
ex.solved_num = CTF_flags.objects.filter(ctf=ex).count()
|
ex.solved_num = CTF_flags.objects.filter(ctf=ex).count()
|
||||||
ex.solved = ex.solved_by(request.user)
|
ex.solved = ex.solved_by(request.user)
|
||||||
return render(request, 'ctfs/ctfs_list.html', {'ctfs' : ctfs, 'cat' : cat})
|
return render(request, 'ctfs/ctfs_list.html', {'ctfs' : ctfs, 'cat' : cat})
|
||||||
|
|
||||||
def ctf(request, cat_slug, ctf_slug):
|
def ctf(request, cat_slug, ctf_slug):
|
||||||
ctf_info = get_object_or_404(CTF, slug=ctf_slug)
|
ctf_info = get_object_or_404(CTF, slug=ctf_slug, event=None)
|
||||||
flagged = False
|
flagged = False
|
||||||
solved_list = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
|
solved_list = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
|
||||||
description = get_description_by_lang(ctf_info)
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
if CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
|
if CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
|
||||||
flagged = True
|
flagged = True
|
||||||
|
@ -39,19 +44,20 @@ def ctf(request, cat_slug, ctf_slug):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
form = submit_flag(data=request.POST)
|
form = submit_flag(data=request.POST)
|
||||||
if flagged == False and form.is_valid():
|
if flagged == False and form.is_valid():
|
||||||
if CTF.objects.filter(flag=request.POST.get('flag'), slug=ctf_slug):
|
if CTF.objects.filter(flag=request.POST.get('flag'), slug=ctf_slug, event=None):
|
||||||
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now())
|
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now())
|
||||||
new.save()
|
new.save()
|
||||||
profil = UserProfileInfo.objects.get(user=request.user)
|
profil = UserProfileInfo.objects.get(user=request.user)
|
||||||
profil.last_submission_date = timezone.now()
|
profil.last_submission_date = timezone.now()
|
||||||
profil.score += ctf_info.points
|
profil.score += ctf_info.points
|
||||||
profil.save()
|
profil.save()
|
||||||
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'valitated': True, 'description': description, 'date': timezone.now()})
|
actualize_points(ctf_info)
|
||||||
|
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'valitated': True, 'date': timezone.now()})
|
||||||
else:
|
else:
|
||||||
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'failed': True, 'description': description, 'date': timezone.now()})
|
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'failed': True, 'date': timezone.now()})
|
||||||
else:
|
else:
|
||||||
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': True, 'description': description, 'date': timezone.now()})
|
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': True, 'date': timezone.now()})
|
||||||
else:
|
else:
|
||||||
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'description': description, 'date': timezone.now()})
|
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'date': timezone.now()})
|
||||||
else:
|
else:
|
||||||
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': flagged, 'description': description, 'date': timezone.now()})
|
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': flagged, 'date': timezone.now()})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Event, EventPlayer, Team
|
from .models import Event, EventPlayer, Team, Bonus
|
||||||
|
|
||||||
@admin.register(Event)
|
@admin.register(Event)
|
||||||
class event(admin.ModelAdmin):
|
class event(admin.ModelAdmin):
|
||||||
|
@ -8,6 +8,27 @@ class event(admin.ModelAdmin):
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['name', 'slug', 'description', 'password']
|
search_fields = ['name', 'slug', 'description', 'password']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
groups = list(request.user.groups.values_list('name', flat=True))
|
||||||
|
return qs.filter(name__in=groups)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.name).exists()
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.name).exists()
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
@admin.register(EventPlayer)
|
@admin.register(EventPlayer)
|
||||||
class score(admin.ModelAdmin):
|
class score(admin.ModelAdmin):
|
||||||
#list display
|
#list display
|
||||||
|
@ -17,7 +38,33 @@ class score(admin.ModelAdmin):
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['user__username', 'score', 'event__name']
|
search_fields = ['user__username', 'score', 'event__name']
|
||||||
|
|
||||||
# Register your models here.
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
groups = list(request.user.groups.values_list('name', flat=True))
|
||||||
|
return qs.filter(event__name__in=groups)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_delete_permission(request, obj)
|
||||||
|
|
||||||
@admin.register(Team)
|
@admin.register(Team)
|
||||||
class team(admin.ModelAdmin):
|
class team(admin.ModelAdmin):
|
||||||
|
@ -27,3 +74,36 @@ class team(admin.ModelAdmin):
|
||||||
list_filter = ('event',)
|
list_filter = ('event',)
|
||||||
# search list
|
# search list
|
||||||
search_fields = ['name']
|
search_fields = ['name']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
groups = list(request.user.groups.values_list('name', flat=True))
|
||||||
|
return qs.filter(event__name__in=groups)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if obj is not None:
|
||||||
|
return request.user.groups.filter(name=obj.event.name).exists()
|
||||||
|
return super().has_delete_permission(request, obj)
|
||||||
|
|
||||||
|
@admin.register(Bonus)
|
||||||
|
class bonus(admin.ModelAdmin):
|
||||||
|
#list display
|
||||||
|
list_display = ['points', 'absolute']
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1.5 on 2022-02-12 18:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0007_event_auto_match'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='dynamic',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-02-15 16:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0008_event_dynamic'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventplayer',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 3.2.11 on 2022-05-30 07:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0011_migration_campus'),
|
||||||
|
('events', '0009_auto_20220215_1706'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='campus',
|
||||||
|
field=models.ManyToManyField(blank=True, to='accounts.Campus'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 3.2.11 on 2023-09-17 17:00
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0010_event_campus'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bonus',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('absolute', models.BooleanField(default=True)),
|
||||||
|
('points', models.CharField(max_length=100, validators=[django.core.validators.int_list_validator])),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='bonus',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.bonus'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.11 on 2023-09-17 18:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0011_bonus_points'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='bonus',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.bonus'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventplayer',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.team'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,9 +1,17 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.models import timezone
|
from django.contrib.auth.models import timezone
|
||||||
|
from django.core.validators import int_list_validator
|
||||||
import uuid
|
import uuid
|
||||||
|
from accounts.models import Campus
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
class Bonus(models.Model):
|
||||||
|
absolute = models.BooleanField(default=True)
|
||||||
|
points = models.CharField(validators=[int_list_validator], max_length=100)
|
||||||
|
def __str__(self):
|
||||||
|
return self.points
|
||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
|
@ -16,6 +24,9 @@ class Event(models.Model):
|
||||||
slug = models.SlugField(max_length=55)
|
slug = models.SlugField(max_length=55)
|
||||||
team_size = models.PositiveIntegerField(default=1)
|
team_size = models.PositiveIntegerField(default=1)
|
||||||
auto_match = models.BooleanField(default=False)
|
auto_match = models.BooleanField(default=False)
|
||||||
|
dynamic = models.BooleanField(default=False)
|
||||||
|
campus = models.ManyToManyField(Campus, blank=True)
|
||||||
|
bonus = models.ForeignKey(Bonus, null=True, on_delete=models.SET_NULL, blank=True)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -27,15 +38,13 @@ class Team(models.Model):
|
||||||
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
||||||
auto = models.BooleanField(default=False)
|
auto = models.BooleanField(default=False)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class EventPlayer(models.Model):
|
class EventPlayer(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
score = models.PositiveIntegerField(default=0, db_index=True)
|
score = models.PositiveIntegerField(default=0, db_index=True)
|
||||||
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
|
||||||
team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True)
|
team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-score', 'last_submission_date', 'user__username']
|
ordering = ['-score', 'last_submission_date', 'user__username']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from django.contrib import sitemaps
|
||||||
|
from django.urls import reverse
|
||||||
|
from .models import Event
|
||||||
|
|
||||||
|
class EventsSitemap(sitemaps.Sitemap):
|
||||||
|
changefreq = "daily"
|
||||||
|
priority = 0.7
|
||||||
|
i18n = True
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return Event.objects.all()
|
||||||
|
|
||||||
|
def location(self, obj):
|
||||||
|
return reverse('events:event_info', kwargs={'event_slug': obj.slug})
|
||||||
|
|
||||||
|
class StaticViewSitemap(sitemaps.Sitemap):
|
||||||
|
priority = 0.7
|
||||||
|
changefreq = 'daily'
|
||||||
|
i18n = True
|
||||||
|
def items(self):
|
||||||
|
return ['events:events']
|
||||||
|
|
||||||
|
def location(self, item):
|
||||||
|
return reverse(item)
|
|
@ -4,6 +4,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
|
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h3>{{ event.name }}</h3>
|
<h3>{{ event.name }}</h3>
|
||||||
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
|
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
|
||||||
|
@ -11,26 +12,29 @@
|
||||||
|
|
||||||
<div class="ctf-footer">
|
<div class="ctf-footer">
|
||||||
<div class="col-sm-8 col-md-6 mx-auto">
|
<div class="col-sm-8 col-md-6 mx-auto">
|
||||||
{% if logged == True%}
|
{% if logged == True%}
|
||||||
{% if registered == False %}
|
{% if registered == False %}
|
||||||
<span class="message error-msg">{% trans "You need to be registered to the event." %}</span>
|
<span class="message error-msg">{% trans "You need to be registered to the event." %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if exist == True %}
|
{% if invalid == True %}
|
||||||
<span class="message error-msg">{% trans "Name already taken." %}</span>
|
<span class="message error-msg">{% trans "Invalid characters in name" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if exist == True %}
|
||||||
|
<span class="message error-msg">{% trans "Name already taken." %}</span>
|
||||||
|
{% endif %}
|
||||||
<h2>Create Team</h2>
|
<h2>Create Team</h2>
|
||||||
<form method="post" action="{% url 'events:create_team' event_slug=event.slug %}" class="create-team-form">
|
<form method="post" action="{% url 'events:create_team' event_slug=event.slug %}" class="create-team-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control" type="text" name="teamname" placeholder="{% trans "Team name" %} *" maxlength="150" required="" id="id_teamname"></br>
|
<input class="form-control" type="text" name="teamname" placeholder="{% trans "Team name" %} *" maxlength="150" required="" id="id_teamname"></br>
|
||||||
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
||||||
<input type="submit" name="" class="form-control" value="{% trans "Create Team" %}">
|
<input type="submit" name="" class="form-control" value="{% trans "Create Team" %}">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,24 +42,26 @@
|
||||||
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">{{ event.name }}</li>
|
<li class="list-group-item">{{ event.name }}</li>
|
||||||
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y'}}</li>
|
<li class="list-group-item">{% trans "Starts at" %} : <span style="position:absolute;right: 15px;">{{ event.start_date | date:'H:i d-m-y'}}</span></li>
|
||||||
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y'}}</li>
|
<li class="list-group-item">{% trans "Ends at" %} : <span style="position:absolute;right: 15px;">{{ event.end_date | date:'H:i d-m-y'}}</span></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">{% trans "Manage my team" %}</li>
|
||||||
|
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">→{% trans "Join Team" %}</a>
|
||||||
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
|
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
|
||||||
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a>
|
</ul>
|
||||||
</ul>
|
{% if event.auto_match %}
|
||||||
{% if event.auto_match %}
|
<ul class="list-group">
|
||||||
<ul class="list-group">
|
<li class="list-group-item">{% trans "Auto-matching" %}</li>
|
||||||
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
|
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
|
||||||
{%csrf_token%}
|
{%csrf_token%}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<input class="form-control" type="submit" value="{% trans "Find me a team !" %}">
|
<input class="form-control" type="submit" value="{% trans "Find me a team !" %}">
|
||||||
</li>
|
</li>
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
|
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h2>{% trans "Event" %} - {{ event.name }}</h2>
|
<h1>{% trans "Event" %} - {{ event.name }}</h1>
|
||||||
<h4>{{ ctf.name }}</h4>
|
<h4>{{ ctf.name }}</h4>
|
||||||
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
|
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,16 +20,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ctf-footer">
|
<div class="ctf-footer">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
|
||||||
{% if subisover == True %}
|
|
||||||
<span class="message error-msg">{% trans "Subscriptions is over." %}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if alreadyregistered == True %}
|
|
||||||
<span class="message error-msg">{% trans "You're already registered to this event." %}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if congrat == True %}
|
{% if congrat == True %}
|
||||||
<p>{% trans "Congratulation !" %}</p>
|
<p>{% trans "Congratulation !" %}</p>
|
||||||
|
{% if bonus|add:"0" > 0 %}
|
||||||
|
<p>{% trans "Bonus points awarded" %} : {{ bonus }}</p>
|
||||||
|
{% endif %}
|
||||||
{% elif alreadyflag == True %}
|
{% elif alreadyflag == True %}
|
||||||
<p>{% trans "Already flagged" %}</p>
|
<p>{% trans "Already flagged" %}</p>
|
||||||
{% elif eventisover == True %}
|
{% elif eventisover == True %}
|
||||||
|
@ -37,7 +32,9 @@
|
||||||
{% elif errorform == True %}
|
{% elif errorform == True %}
|
||||||
<p>{% trans "Error while processing your request. (Invalid Form)" %}</p>
|
<p>{% trans "Error while processing your request. (Invalid Form)" %}</p>
|
||||||
{% elif notsub == True %}
|
{% elif notsub == True %}
|
||||||
<span class="message error-msg">{% trans "Error: you're not registered to this event, so you can't register scores, fucking logic." %}</span>
|
<span class="message error-msg">{% trans "You must register to the event before submitting flags." %}</span>
|
||||||
|
{% elif noteam == True %}
|
||||||
|
<span class="message error-msg">{% trans "This is a team event, please create or join a team before submitting flags." %}</span>
|
||||||
{% if ctf.ctf_url %}
|
{% if ctf.ctf_url %}
|
||||||
<a class="begin-ctf-link" target="_blank" href="{{ ctf.ctf_url }}">{% trans "Start the challenge" %}</a></br>
|
<a class="begin-ctf-link" target="_blank" href="{{ ctf.ctf_url }}">{% trans "Start the challenge" %}</a></br>
|
||||||
{% elif ctf.file %}
|
{% elif ctf.file %}
|
||||||
|
@ -102,6 +99,9 @@
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">{% trans "Author" %} : <a style="position:absolute;right: 15px;" class="profile_link {{is_member}}" href="{% url 'accounts:profile' user_name=ctf.author.username %}">{{ ctf.author.username }}</a></li>
|
<li class="list-group-item">{% trans "Author" %} : <a style="position:absolute;right: 15px;" class="profile_link {{is_member}}" href="{% url 'accounts:profile' user_name=ctf.author.username %}">{{ ctf.author.username }}</a></li>
|
||||||
<li class="list-group-item">{% trans "Point reward" %} : <span style="position:absolute;right: 15px;">{{ ctf.points }}</span></li>
|
<li class="list-group-item">{% trans "Point reward" %} : <span style="position:absolute;right: 15px;">{{ ctf.points }}</span></li>
|
||||||
|
{% if ctf.event.bonus %}
|
||||||
|
<li class="list-group-item">{% trans "Speed Bonuses" %} : <span style="position:absolute;right: 15px;">{{ bonus_points }}</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="event-block">
|
<div class="event-block">
|
||||||
<div class="event-head" style="background-image:linear-gradient(180deg, rgba(102,102,102,0.3) 100%, rgba(29,29,29,1) 100%),url('{{ event.img }}');">
|
<div class="event-head" style="background-image:linear-gradient(180deg, rgba(102,102,102,0.3) 100%, rgba(29,29,29,1) 100%),url('{{ event.img }}');">
|
||||||
<h3>{{ event.name }}</h3>
|
<h1>{{ event.name }}</h1>
|
||||||
{% if ended == True %}
|
{% if ended == True %}
|
||||||
<small>{% trans "This event is over." %}</small>
|
<small>{% trans "This event is over." %}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="event-footer">
|
<div class="event-footer">
|
||||||
{% if begun == True %}
|
{% if begun == True or is_event_manager == True %}
|
||||||
<h4>{% trans "Challenges" %}</h4>
|
<h4>{% trans "Challenges" %}</h4>
|
||||||
|
|
||||||
{% if ctfs %}
|
{% if ctfs %}
|
||||||
|
|
|
@ -8,22 +8,38 @@
|
||||||
<h3>{{ event.name }}</h3>
|
<h3>{{ event.name }}</h3>
|
||||||
<small>{% trans "This event start at" %} : {{ event.start_date }}</small>
|
<small>{% trans "This event start at" %} : {{ event.start_date }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ctf-footer">
|
<div class="ctf-footer">
|
||||||
{% if logged == True %}
|
{% if logged == True %}
|
||||||
{% if wrongpwd == True %}
|
{% if userHasCampus == False %}
|
||||||
<span class="message error-msg">{% trans "Wrong password submited." %}</span>
|
<span class="message error-msg">
|
||||||
|
{% trans "This event is reserved for one or more 42 campuses. If you have not connected your intranet to 42CTF, you can do so with this button: " %}
|
||||||
|
<form action="{% url 'accounts:connections-connect-intra42' %}" method='POST' class="form-inline p-2">
|
||||||
|
{%csrf_token%}
|
||||||
|
<button class="btn btn-dark" type="submit">{% trans "Connect 42" %}</button>
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if alreadyregistered == True %}
|
{% if campusCanJoin == False %}
|
||||||
<span class="message error-msg">{% trans "You're already registered to this event." %}</span>
|
<span class="message error-msg">
|
||||||
|
{% trans "This event is reserved for one or more 42 campuses. And unfortunately your campus can't participate. Do not hesitate to contact us to organize an event on your campus!" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if userHasCampus == True and campusCanJoin == True %}
|
||||||
|
{% if wrongpwd == True %}
|
||||||
|
<span class="message error-msg">{% trans "Wrong password submited." %}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if alreadyregistered == True %}
|
||||||
|
<span class="message error-msg">{% trans "You're already registered to this event." %}</span>
|
||||||
|
{% endif %}
|
||||||
|
<h4>{% trans "This event is password protected" %}</h4>
|
||||||
|
<small>{% trans "You need to submit the event password to gain access to this event." %}</small>
|
||||||
|
<form method="post" action="{% url 'events:submit_pwd' event_slug=event.slug %}" class="submitflag-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="text" name="password" maxlength="48" required="">
|
||||||
|
<input class="form-control" type="submit" value=">">
|
||||||
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h4>{% trans "This event is password protected" %}</h4>
|
|
||||||
<small>{% trans "You need to submit the event password to gain access to this event." %}</small>
|
|
||||||
<form method="post" action="{% url 'events:submit_pwd' event_slug=event.slug %}" class="submitflag-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="text" name="password" maxlength="48" required="">
|
|
||||||
<input class="form-control" type="submit" value=">">
|
|
||||||
</form>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -35,7 +51,7 @@
|
||||||
<li class="list-group-item">{{ event.name }}</li>
|
<li class="list-group-item">{{ event.name }}</li>
|
||||||
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y'}}</li>
|
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y'}}</li>
|
||||||
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y'}}</li>
|
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y'}}</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12 ctf-head">
|
||||||
<h3>{% trans "Events" %}</h3>
|
<h1>{% trans "Events" %}</h1>
|
||||||
</div>
|
</div>
|
||||||
{% if events %}
|
{% if events %}
|
||||||
{% for ev in events %}
|
{% for ev in events %}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-9">
|
<div class="col-sm-12 col-md-9">
|
||||||
<div class="ctf-block">
|
<div class="ctf-block">
|
||||||
|
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
|
||||||
<div class="ctf-head">
|
<div class="ctf-head">
|
||||||
<h3>{{ event.name }}</h3>
|
<h3>{{ event.name }}</h3>
|
||||||
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
|
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
|
||||||
|
@ -11,18 +12,18 @@
|
||||||
|
|
||||||
<div class="ctf-footer">
|
<div class="ctf-footer">
|
||||||
<div class="col-sm-8 col-md-6 mx-auto">
|
<div class="col-sm-8 col-md-6 mx-auto">
|
||||||
{% if logged == True%}
|
{% if logged == True%}
|
||||||
{% if registered == False %}
|
{% if registered == False %}
|
||||||
<span class="message error-msg">{% trans "You need to be registered to the event." %}</span>
|
<span class="message error-msg">{% trans "You need to be registered to the event." %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if notexist == True %}
|
{% if notexist == True %}
|
||||||
<span class="message error-msg">{% trans "Team does not exist." %}</span>
|
<span class="message error-msg">{% trans "Team does not exist." %}</span>
|
||||||
{% elif wrongpwd == True %}
|
{% elif wrongpwd == True %}
|
||||||
<span class="message error-msg">{% trans "Wrong password submited." %}</span>
|
<span class="message error-msg">{% trans "Wrong password submited." %}</span>
|
||||||
{% elif max == True %}
|
{% elif max == True %}
|
||||||
<span class="message error-msg">{% trans "Maximum size reached." %}</span>
|
<span class="message error-msg">{% trans "Maximum size reached." %}</span>
|
||||||
{% elif exist == True %}
|
{% elif exist == True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Join Team</h2>
|
<h2>Join Team</h2>
|
||||||
<form method="post" action="{% url 'events:join_team' event_slug=event.slug %}" class="join-team-form">
|
<form method="post" action="{% url 'events:join_team' event_slug=event.slug %}" class="join-team-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -31,11 +32,11 @@
|
||||||
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
|
||||||
<input type="submit" name="" class="form-control" value="{% trans "Join Team" %}">
|
<input type="submit" name="" class="form-control" value="{% trans "Join Team" %}">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
<h4>{% trans "You need to be logged to access this event." %}</h4>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,24 +44,26 @@
|
||||||
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">{{ event.name }}</li>
|
<li class="list-group-item">{{ event.name }}</li>
|
||||||
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y' }}</li>
|
<li class="list-group-item">{% trans "Starts at" %} : <span style="position:absolute;right: 15px;">{{ event.start_date | date:'H:i d-m-y'}}</span></li>
|
||||||
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y' }}</li>
|
<li class="list-group-item">{% trans "Ends at" %} : <span style="position:absolute;right: 15px;">{{ event.end_date | date:'H:i d-m-y'}}</span></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a>
|
<li class="list-group-item">{% trans "Manage my team" %}</li>
|
||||||
|
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a>
|
||||||
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
|
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
|
||||||
</ul>
|
</ul>
|
||||||
{% if event.auto_match %}
|
{% if event.auto_match %}
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
|
<li class="list-group-item">{% trans "Auto-matching" %}</li>
|
||||||
{%csrf_token%}
|
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
|
||||||
<li class="list-group-item">
|
{%csrf_token%}
|
||||||
<input class="form-control" type="submit" value="{% trans "Find me a team !" %}">
|
<li class="list-group-item">
|
||||||
</li>
|
<input class="form-control" type="submit" value="{% trans "Find me a team !" %}">
|
||||||
</form>
|
</li>
|
||||||
</ul>
|
</form>
|
||||||
{% endif %}
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -45,14 +45,28 @@
|
||||||
{% for p in members %}
|
{% for p in members %}
|
||||||
<li class="list-group-item"><a class="profile_link" href="{% url 'accounts:profile' user_name=p.user.username %}">{{ p.user.username }}</a></li>
|
<li class="list-group-item"><a class="profile_link" href="{% url 'accounts:profile' user_name=p.user.username %}">{{ p.user.username }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<form method='POST' action="{% url 'events:leave_team' event_slug=player.event.slug %}">
|
</ul>
|
||||||
|
<form method='POST' action="{% url 'events:leave_team' event_slug=player.event.slug %}">
|
||||||
|
{%csrf_token%}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<input class="form-control" type="submit" value="{% trans "Leave Team" %}">
|
||||||
|
</li>
|
||||||
|
</form>
|
||||||
|
{% if player.team.auto == False and player.event.auto_match == True %}
|
||||||
|
<form method='POST' action="{% url 'events:open_team' event_slug=player.event.slug %}">
|
||||||
{%csrf_token%}
|
{%csrf_token%}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<input class="form-control" type="submit" value="{% trans "Leave Team" %}">
|
<input class="form-control" type="submit" value="{% trans "Open to automatching" %}">
|
||||||
</li>
|
</li>
|
||||||
</form>
|
</form>
|
||||||
|
{% elif player.event.auto_match == True %}
|
||||||
</ul>
|
<form method='POST' action="{% url 'events:close_team' event_slug=player.event.slug %}">
|
||||||
|
{%csrf_token%}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<input class="form-control" type="submit" value="{% trans "Close to automatching" %}">
|
||||||
|
</li>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load key_value %}
|
||||||
|
{% load is_member %}
|
||||||
|
{% ismember user.userprofileinfo as is_member %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-9">
|
||||||
|
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
|
||||||
|
<div>
|
||||||
|
<h4>{% trans "Challenges Solved by" %} <span class="{{ is_member }}">{{ user.username }} - {{ event.name }}</span></h4>
|
||||||
|
{% if solves%}
|
||||||
|
|
||||||
|
<div class="table table-dark">
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="time-chart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-dark">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{% trans "Challenge Name" %}</th>
|
||||||
|
<th scope="col">{% trans "Category" %}</th>
|
||||||
|
<th scope="col">{% trans "Points" %}</th>
|
||||||
|
<th scope="col">{% trans "Bonus" %}</th>
|
||||||
|
<th scope="col">{% trans "Date" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for s in solves %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="{% url 'events:event_chall_info' event_slug=event.slug chall_slug=s.ctf.slug %}">{{ s.ctf.name }}</a></th>
|
||||||
|
<td>{{ s.ctf.category.name}}</td>
|
||||||
|
<td>{{ s.ctf.points }}</td>
|
||||||
|
<td>{{ s.bonus }}</td>
|
||||||
|
<td>{{ s.flag_date|date:"Y-m-d H:i:s" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "It seems that this user has not solved any challenge yet..." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item {{ is_member }}">{{ user.username }}</li>
|
||||||
|
<li class="list-group-item">{% trans "Score" %} : {{ score }}</li>
|
||||||
|
<li class="list-group-item">{% trans "Rank" %} : {{ rank }}</li>
|
||||||
|
{% if user.userprofileinfo.portfolio_site %}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a href="{{ user.userprofileinfo.portfolio_site }}" target="_blank">
|
||||||
|
{{ user.userprofileinfo.portfolio_site }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if member %}
|
||||||
|
<li class="list-group-item is-member">{% trans "Status: Member" %}</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="list-group-item">{% trans "Status: Visitor" %}</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">{% trans "Categories stats" %}</li>
|
||||||
|
{% for cat in catsDatas %}
|
||||||
|
<li class="list-group-item" style="padding-bottom: 3;padding-top: 0;">
|
||||||
|
<span>{{ cat.0 }}</span>
|
||||||
|
<div class="progress">
|
||||||
|
{% if cat.3 == '0' %}
|
||||||
|
<div class="progress-bar bg-success" role="progressbar" style="width: 0%;color:#d9d9d9;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0 %</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="progress-bar bg-success" role="progressbar" style="width: {{ cat.3 }}%" aria-valuenow="{{ cat.3 }}" aria-valuemin="0" aria-valuemax="100">{{ cat.3 }} %</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">{% trans "Challenges created" %}</li>
|
||||||
|
{% if created %}
|
||||||
|
{% for creat in created %}
|
||||||
|
<li class="list-group-item"><a href="{% url 'ctf' cat_slug=creat.category.slug ctf_slug=creat.slug %}">{{ creat.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="list-group-item">{% trans "It seems that this user has not created any challenge yet..." %}</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.highcharts.com/highcharts.src.js"></script>
|
||||||
|
<script>
|
||||||
|
Highcharts.theme={colors:["#2b908f","#90ee7e","#f45b5b","#7798BF","#aaeeee","#ff0066","#eeaaee","#55BF3B","#DF5353","#7798BF","#aaeeee"],chart:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:1,y2:1},stops:[[0,"#1D1D1D"],[1,"#1D1D1D"]]},style:{fontFamily:"'Unica One', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",textTransform:"uppercase",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3",textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#F0F0F3",style:{fontSize:"13px"}},marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{backgroundColor:"#1D1D1D",itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"},title:{style:{color:"#C0C0C0"}}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}},rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}},scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"}};
|
||||||
|
|
||||||
|
Highcharts.setOptions(Highcharts.theme);
|
||||||
|
|
||||||
|
Highcharts.chart('time-chart', {
|
||||||
|
title: {
|
||||||
|
text: 'Points earned for each category'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
title: {
|
||||||
|
text: 'Points earned'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {
|
||||||
|
formatter: function() {
|
||||||
|
return Highcharts.dateFormat('%d.%b %Y',
|
||||||
|
this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
layout: 'vertical',
|
||||||
|
align: 'right',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pointStart: {{ user.date_joined|timestamp_fromdate }},
|
||||||
|
series: {
|
||||||
|
label: {
|
||||||
|
connectorAllowed: false
|
||||||
|
},
|
||||||
|
allowPointSelect: true,
|
||||||
|
marker: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Total',
|
||||||
|
data: {{ solved|safe }}
|
||||||
|
},
|
||||||
|
{% for cat in cats %}
|
||||||
|
{
|
||||||
|
name: '{{ cat.name }}',
|
||||||
|
data: {{ pointDatas|keyvalue:cat.name|safe }},
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
responsive: {
|
||||||
|
rules: [{
|
||||||
|
condition: {
|
||||||
|
maxWidth: 500
|
||||||
|
},
|
||||||
|
chartOptions: {
|
||||||
|
legend: {
|
||||||
|
layout: 'horizontal',
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'bottom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from .sitemaps import StaticViewSitemap, EventsSitemap
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = "events"
|
app_name = "events"
|
||||||
|
|
||||||
|
sitemaps = {
|
||||||
|
'events': EventsSitemap(),
|
||||||
|
'static': StaticViewSitemap(),
|
||||||
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.events, name='events'),
|
path('', views.events, name='events'),
|
||||||
|
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
|
||||||
path('<str:event_slug>', views.event, name='event_info'),
|
path('<str:event_slug>', views.event, name='event_info'),
|
||||||
path('<str:event_slug>/challenge/<str:chall_slug>', views.chall_event_info, name='event_chall_info'),
|
path('<str:event_slug>/challenge/<str:chall_slug>', views.chall_event_info, name='event_chall_info'),
|
||||||
path('pwd/<str:event_slug>', views.submit_pwd, name='submit_pwd'),
|
path('pwd/<str:event_slug>', views.submit_pwd, name='submit_pwd'),
|
||||||
|
@ -17,4 +25,6 @@ urlpatterns = [
|
||||||
path('<str:event_slug>/manage_team', views.manage_team, name='manage_team'),
|
path('<str:event_slug>/manage_team', views.manage_team, name='manage_team'),
|
||||||
path('<str:event_slug>/leave_team', views.leave_team, name='leave_team'),
|
path('<str:event_slug>/leave_team', views.leave_team, name='leave_team'),
|
||||||
path('find_team/<str:event_slug>', views.find_team, name='find_team'),
|
path('find_team/<str:event_slug>', views.find_team, name='find_team'),
|
||||||
|
path('<str:event_slug>/open_team', views.open_team, name='open_team'),
|
||||||
|
path('<str:event_slug>/close_team', views.close_team, name='close_team'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,9 +4,11 @@ from django.contrib.auth.models import timezone
|
||||||
from ..forms import submit_flag
|
from ..forms import submit_flag
|
||||||
from ..models import Event, EventPlayer, Team
|
from ..models import Event, EventPlayer, Team
|
||||||
from ctfs.models import CTF, CTF_flags, Category
|
from ctfs.models import CTF, CTF_flags, Category
|
||||||
|
from accounts.models import UserProfileInfo
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from math import log
|
||||||
|
|
||||||
def get_description_by_lang(ctf):
|
def get_description_by_lang(ctf):
|
||||||
lang = get_language()
|
lang = get_language()
|
||||||
|
@ -21,6 +23,49 @@ def get_description_by_lang(ctf):
|
||||||
ret = ctf.description_ru
|
ret = ctf.description_ru
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def actualize_points(ctf):
|
||||||
|
solves = CTF_flags.objects.filter(ctf=ctf)
|
||||||
|
nb_solves = len(solves)
|
||||||
|
|
||||||
|
new_points = max(50 - int(log(nb_solves)*2.5)*5, 5)
|
||||||
|
|
||||||
|
if new_points != ctf.points:
|
||||||
|
diff = ctf.points - new_points
|
||||||
|
ctf.points = new_points
|
||||||
|
ctf.save()
|
||||||
|
for s in solves:
|
||||||
|
player = EventPlayer.objects.get(event=ctf.event, user=s.user)
|
||||||
|
player.score -= diff
|
||||||
|
player.save()
|
||||||
|
if player.team:
|
||||||
|
player.team.score -= diff
|
||||||
|
player.team.save()
|
||||||
|
|
||||||
|
def compute_bonus_points(ctf):
|
||||||
|
if not ctf.event.bonus:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
solves = CTF_flags.objects.filter(ctf=ctf)
|
||||||
|
bonuses = ctf.event.bonus.points.split(',')
|
||||||
|
|
||||||
|
if len(solves) >= len(bonuses):
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
if ctf.event.bonus.absolute == True:
|
||||||
|
return int(bonuses[len(solves)])
|
||||||
|
else:
|
||||||
|
return int(bonuses[len(solves)]) * ctf.points // 100
|
||||||
|
|
||||||
|
def format_bonus_points(ctf):
|
||||||
|
if not ctf.event.bonus:
|
||||||
|
return None
|
||||||
|
|
||||||
|
bonuses = ctf.event.bonus.points.split(',')
|
||||||
|
if ctf.event.bonus.absolute == True:
|
||||||
|
return ''.join([b + ', ' for b in bonuses])[:-2]
|
||||||
|
return ''.join([str(ctf.points * int(b) // 100) + ', ' for b in bonuses])[:-2]
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
def events(request):
|
def events(request):
|
||||||
list_events = Event.objects.filter().order_by('-end_date', 'start_date')
|
list_events = Event.objects.filter().order_by('-end_date', 'start_date')
|
||||||
|
@ -29,7 +74,9 @@ def events(request):
|
||||||
def chall_event_info(request, event_slug, chall_slug):
|
def chall_event_info(request, event_slug, chall_slug):
|
||||||
event_info = get_object_or_404(Event, slug=event_slug)
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
ctf_info = get_object_or_404(CTF, event__slug=event_info.slug, slug=chall_slug)
|
ctf_info = get_object_or_404(CTF, event__slug=event_info.slug, slug=chall_slug)
|
||||||
if timezone.now() < ctf_info.pub_date:
|
|
||||||
|
is_event_manager = request.user.groups.filter(name=event_info.name).exists() or request.user.is_superuser
|
||||||
|
if timezone.now() < ctf_info.pub_date and not is_event_manager:
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
eventisover = False
|
eventisover = False
|
||||||
alreadyflag = False
|
alreadyflag = False
|
||||||
|
@ -37,14 +84,16 @@ def chall_event_info(request, event_slug, chall_slug):
|
||||||
wrongflag = False
|
wrongflag = False
|
||||||
errorform = False
|
errorform = False
|
||||||
notsub = False
|
notsub = False
|
||||||
|
noteam = False
|
||||||
player = None
|
player = None
|
||||||
|
bonus = 0
|
||||||
if request.user.is_authenticated and not request.user.is_staff:
|
if request.user.is_authenticated and not request.user.is_staff:
|
||||||
player = EventPlayer.objects.filter(event=event_info, user=request.user)
|
player = EventPlayer.objects.filter(event=event_info, user=request.user)
|
||||||
if not player:
|
if not player:
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
elif not request.user.is_authenticated:
|
elif not request.user.is_authenticated:
|
||||||
return redirect('accounts:signin')
|
return redirect('accounts:signin')
|
||||||
if request.GET.get('EventIsOver'):
|
if request.GET.get('EventIsOver') or timezone.now() > event_info.end_date:
|
||||||
eventisover = True
|
eventisover = True
|
||||||
if request.GET.get('AlreadyFlagged'):
|
if request.GET.get('AlreadyFlagged'):
|
||||||
alreadyflag = True
|
alreadyflag = True
|
||||||
|
@ -56,6 +105,10 @@ def chall_event_info(request, event_slug, chall_slug):
|
||||||
errorform = True
|
errorform = True
|
||||||
if request.GET.get('NotRegistered'):
|
if request.GET.get('NotRegistered'):
|
||||||
notsub = True
|
notsub = True
|
||||||
|
if request.GET.get('NoTeam'):
|
||||||
|
noteam = True
|
||||||
|
bonus = request.GET.get('Bonus')
|
||||||
|
bonus_points = format_bonus_points(ctf_info)
|
||||||
solved_challs = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
|
solved_challs = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
|
||||||
solved_list = []
|
solved_list = []
|
||||||
for s in solved_challs:
|
for s in solved_challs:
|
||||||
|
@ -65,56 +118,65 @@ def chall_event_info(request, event_slug, chall_slug):
|
||||||
solved_list.append([s.user, s.flag_date])
|
solved_list.append([s.user, s.flag_date])
|
||||||
description = get_description_by_lang(ctf_info)
|
description = get_description_by_lang(ctf_info)
|
||||||
return render(request, 'events/ctf_info.html', { 'ctf' : ctf_info, 'event':event_info, 'solved_list': solved_list, 'description': description, 'eventisover': eventisover, 'alreadyflag': alreadyflag,
|
return render(request, 'events/ctf_info.html', { 'ctf' : ctf_info, 'event':event_info, 'solved_list': solved_list, 'description': description, 'eventisover': eventisover, 'alreadyflag': alreadyflag,
|
||||||
'congrat': congrat, 'wrongflag': wrongflag, 'errorform': errorform, 'notsub': notsub})
|
'congrat': congrat, 'wrongflag': wrongflag, 'errorform': errorform, 'notsub': notsub, 'noteam':noteam, 'bonus':bonus, 'bonus_points':bonus_points})
|
||||||
|
|
||||||
def event(request, event_slug):
|
def event(request, event_slug):
|
||||||
event_info = get_object_or_404(Event, slug=event_slug)
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
IsRegistered = False
|
|
||||||
wrongpwd = False
|
wrongpwd = False
|
||||||
alreadyregistered = False
|
alreadyregistered = False
|
||||||
subisover = False
|
subisover = False
|
||||||
|
|
||||||
|
is_event_manager = request.user.groups.filter(name=event_info.name).exists() or request.user.is_superuser
|
||||||
|
|
||||||
|
ended = (timezone.now() >= event_info.end_date)
|
||||||
|
begun = (timezone.now() >= event_info.start_date)
|
||||||
|
|
||||||
|
if is_event_manager: # we want to see all the challenges
|
||||||
|
challenges = CTF.objects.filter(event=event_info).order_by('category', 'points')
|
||||||
|
else:
|
||||||
|
challenges = CTF.objects.filter(event=event_info, pub_date__lte=timezone.now()).order_by('category', 'points')
|
||||||
|
|
||||||
|
if event_info.team_size == 1:
|
||||||
|
solved_list = EventPlayer.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'user__username')
|
||||||
|
else:
|
||||||
|
solved_list = Team.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'name')
|
||||||
|
|
||||||
if request.GET.get('WrongPassword'):
|
if request.GET.get('WrongPassword'):
|
||||||
wrongpwd = True
|
wrongpwd = True
|
||||||
if request.GET.get('AlreadyRegistered'):
|
if request.GET.get('AlreadyRegistered'):
|
||||||
alreadyregistered = True
|
alreadyregistered = True
|
||||||
if request.GET.get('SubscriptionIsOver'):
|
if request.GET.get('SubscriptionIsOver'):
|
||||||
subisover = True
|
subisover = True
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
player = EventPlayer.objects.get(event=event_info, user=request.user)
|
EventPlayer.objects.get(event=event_info, user=request.user)
|
||||||
|
return render(request, 'events/event_info.html', {'event' : event_info, 'IsRegistered': True, 'ctfs': challenges, 'solved_list':solved_list,
|
||||||
|
'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover, 'is_event_manager':is_event_manager})
|
||||||
except:
|
except:
|
||||||
player = None
|
pass
|
||||||
if player:
|
|
||||||
IsRegistered = True
|
if (event_info.campus.all() or event_info.password) and request.user.is_authenticated is False:
|
||||||
if not player.team and event_info.team_size > 1:
|
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': False})
|
||||||
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
|
||||||
if event_info.password:
|
if event_info.campus.all() and is_event_manager is False:
|
||||||
if request.user.is_authenticated:
|
user = UserProfileInfo.objects.get(user=request.user)
|
||||||
if request.user.is_staff is False:
|
if user.campus is None:
|
||||||
if not player:
|
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': False, 'campusCanJoin': True})
|
||||||
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered})
|
elif user.campus not in event_info.campus.all():
|
||||||
elif not player.team and event_info.team_size > 1:
|
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': True, 'campusCanJoin': False})
|
||||||
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
|
||||||
else:
|
if event_info.password and is_event_manager is False:
|
||||||
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': False, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered})
|
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': True, 'campusCanJoin': True})
|
||||||
ended = False
|
|
||||||
if timezone.now() >= event_info.end_date:
|
return render(request, 'events/event_info.html', {'event' : event_info, 'ctfs': challenges, 'solved_list':solved_list, 'IsRegistered': False,
|
||||||
ended = True
|
'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover, 'is_event_manager':is_event_manager})
|
||||||
begun = False
|
|
||||||
if timezone.now() >= event_info.start_date:
|
|
||||||
begun = True
|
|
||||||
challenges = CTF.objects.filter(event=event_info).order_by('category', 'points')
|
|
||||||
if event_info.team_size == 1:
|
|
||||||
solved_list = EventPlayer.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'user__username')
|
|
||||||
else:
|
|
||||||
solved_list = Team.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'name')
|
|
||||||
return render(request, 'events/event_info.html', {'event' : event_info, 'IsRegistered': IsRegistered, 'ctfs': challenges, 'solved_list':solved_list,
|
|
||||||
'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover})
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def submit_event_flag(request, event_slug, chall_slug):
|
def submit_event_flag(request, event_slug, chall_slug):
|
||||||
ev = get_object_or_404(Event, slug=event_slug)
|
ev = get_object_or_404(Event, slug=event_slug)
|
||||||
response = redirect('events:event_chall_info', event_slug=event_slug, chall_slug=chall_slug)
|
response = redirect('events:event_chall_info', event_slug=event_slug, chall_slug=chall_slug)
|
||||||
|
flagged = False
|
||||||
|
|
||||||
if timezone.now() >= ev.end_date:
|
if timezone.now() >= ev.end_date:
|
||||||
response['Location'] += '?EventIsOver=1'
|
response['Location'] += '?EventIsOver=1'
|
||||||
|
@ -125,13 +187,19 @@ def submit_event_flag(request, event_slug, chall_slug):
|
||||||
if not ctf_info:
|
if not ctf_info:
|
||||||
response['Location'] += '?ChallengeNotFound=1'
|
response['Location'] += '?ChallengeNotFound=1'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
flagged = False
|
try:
|
||||||
player = EventPlayer.objects.get(user=request.user, event=ev)
|
player = EventPlayer.objects.get(event=ev, user=request.user)
|
||||||
|
except:
|
||||||
|
player = None
|
||||||
|
|
||||||
if player:
|
if player:
|
||||||
if ev.team_size == 1 and CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
|
if ev.team_size > 1 and player.team is None:
|
||||||
flagged = True
|
response['Location'] += '?NoTeam=1'
|
||||||
|
return response
|
||||||
|
if ev.team_size == 1:
|
||||||
|
if CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
|
||||||
|
flagged = True
|
||||||
else:
|
else:
|
||||||
solved_list = CTF_flags.objects.filter(ctf=ctf_info)
|
solved_list = CTF_flags.objects.filter(ctf=ctf_info)
|
||||||
for s in solved_list:
|
for s in solved_list:
|
||||||
|
@ -146,18 +214,21 @@ def submit_event_flag(request, event_slug, chall_slug):
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if ctf_info.flag == request.POST.get('flag'):
|
if ctf_info.flag == request.POST.get('flag'):
|
||||||
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now())
|
bonus = compute_bonus_points(ctf_info)
|
||||||
|
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now(), bonus = bonus)
|
||||||
new.save()
|
new.save()
|
||||||
if ctf_info.points > 0:
|
if ctf_info.points > 0:
|
||||||
player.last_submission_date = timezone.now()
|
player.last_submission_date = timezone.now()
|
||||||
player.score += ctf_info.points
|
player.score += (ctf_info.points + bonus)
|
||||||
player.save()
|
player.save()
|
||||||
if player.team:
|
if player.team:
|
||||||
if ctf_info.points > 0:
|
if ctf_info.points > 0:
|
||||||
player.team.last_submission_date = timezone.now()
|
player.team.last_submission_date = timezone.now()
|
||||||
player.team.score += ctf_info.points
|
player.team.score += (ctf_info.points + bonus)
|
||||||
player.team.save()
|
player.team.save()
|
||||||
response['Location'] += '?Congrat=1'
|
if ev.dynamic:
|
||||||
|
actualize_points(ctf_info)
|
||||||
|
response['Location'] += '?Congrat=1&Bonus=' + str(bonus)
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
response['Location'] += '?WrongFlag=1'
|
response['Location'] += '?WrongFlag=1'
|
||||||
|
@ -191,10 +262,8 @@ def submit_pwd(request, event_slug):
|
||||||
else:
|
else:
|
||||||
new = EventPlayer(user=request.user, event=ev)
|
new = EventPlayer(user=request.user, event=ev)
|
||||||
new.save()
|
new.save()
|
||||||
if event_info.team_size > 1:
|
|
||||||
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def register_to_event(request, event_slug):
|
def register_to_event(request, event_slug):
|
||||||
|
@ -214,8 +283,6 @@ def register_to_event(request, event_slug):
|
||||||
else:
|
else:
|
||||||
new = EventPlayer(user=request.user, event=ev, score=0)
|
new = EventPlayer(user=request.user, event=ev, score=0)
|
||||||
new.save()
|
new.save()
|
||||||
if ev.team_size > 1:
|
|
||||||
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -243,7 +310,7 @@ def profile(request, user_name, event_slug):
|
||||||
percent = (solved_count / max_count) * 100
|
percent = (solved_count / max_count) * 100
|
||||||
catsDatas.append([cat.name, solved_count, max_count, '{:.0f}'.format(percent)])
|
catsDatas.append([cat.name, solved_count, max_count, '{:.0f}'.format(percent)])
|
||||||
for flag in solved:
|
for flag in solved:
|
||||||
somme += flag.ctf.points
|
somme += (flag.ctf.points + flag.bonus)
|
||||||
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
|
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
|
||||||
|
|
||||||
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=event_info).order_by('-flag_date')
|
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=event_info).order_by('-flag_date')
|
||||||
|
@ -251,10 +318,10 @@ def profile(request, user_name, event_slug):
|
||||||
somme = 0
|
somme = 0
|
||||||
solved.append([event_info.start_date.timestamp() * 1000, 0])
|
solved.append([event_info.start_date.timestamp() * 1000, 0])
|
||||||
for s in solves.reverse():
|
for s in solves.reverse():
|
||||||
somme += s.ctf.points
|
somme += (s.ctf.points + s.bonus)
|
||||||
solved.append([s.flag_date.timestamp() * 1000,somme])
|
solved.append([s.flag_date.timestamp() * 1000,somme])
|
||||||
|
|
||||||
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
|
return render(request,'events/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
|
||||||
'rank': rank, 'score' : somme, 'cats':cats})
|
'rank': rank, 'score' : player.score, 'cats':cats, 'event': event_info})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,22 +11,25 @@ from random import randint
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def create_team(request, event_slug):
|
def create_team(request, event_slug):
|
||||||
response = redirect('events:create_team', event_slug=event_slug)
|
|
||||||
ev = get_object_or_404(Event, slug=event_slug)
|
ev = get_object_or_404(Event, slug=event_slug)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
teamname = request.POST.get('teamname')
|
||||||
if request.user.is_authenticated and ev.team_size > 1:
|
if request.user.is_authenticated and ev.team_size > 1:
|
||||||
if Team.objects.filter(name=request.POST.get('teamname'), event=ev).exists():
|
if any(c in set('./') for c in teamname):
|
||||||
|
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : False, 'invalid' : True})
|
||||||
|
if Team.objects.filter(name=teamname, event=ev).exists():
|
||||||
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : True})
|
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : True})
|
||||||
new = Team(name=request.POST.get('teamname'), password=request.POST.get('password'), event=ev)
|
new = Team(name=teamname, password=request.POST.get('password'), event=ev)
|
||||||
new.save()
|
new.save()
|
||||||
player = EventPlayer.objects.get(user=request.user, event=ev)
|
player = EventPlayer.objects.get(user=request.user, event=ev)
|
||||||
player.team = new
|
player.team = new
|
||||||
player.save()
|
player.save()
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
|
else:
|
||||||
|
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : False})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def join_team(request, event_slug):
|
def join_team(request, event_slug):
|
||||||
response = redirect('events:join_team', event_slug=event_slug)
|
|
||||||
ev = get_object_or_404(Event, slug=event_slug)
|
ev = get_object_or_404(Event, slug=event_slug)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if request.user.is_authenticated and ev.team_size > 1:
|
if request.user.is_authenticated and ev.team_size > 1:
|
||||||
|
@ -46,9 +49,9 @@ def join_team(request, event_slug):
|
||||||
player = EventPlayer.objects.get(user=request.user, event=ev)
|
player = EventPlayer.objects.get(user=request.user, event=ev)
|
||||||
player.team = team
|
player.team = team
|
||||||
player.save()
|
player.save()
|
||||||
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
else:
|
else:
|
||||||
return render(request, 'events/join_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
return render(request, 'events/join_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def team_info(request, name, event_slug):
|
def team_info(request, name, event_slug):
|
||||||
|
@ -102,6 +105,8 @@ def team_info(request, name, event_slug):
|
||||||
def manage_team(request, event_slug):
|
def manage_team(request, event_slug):
|
||||||
event_info = get_object_or_404(Event, slug=event_slug)
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
||||||
|
if not player.team:
|
||||||
|
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
||||||
members = EventPlayer.objects.filter(team=player.team, event=event_info)
|
members = EventPlayer.objects.filter(team=player.team, event=event_info)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -113,9 +118,10 @@ def manage_team(request, event_slug):
|
||||||
pname = p_form.cleaned_data['name']
|
pname = p_form.cleaned_data['name']
|
||||||
if pname == tname:
|
if pname == tname:
|
||||||
pass
|
pass
|
||||||
else:
|
elif any(c in set('./') for c in pname):
|
||||||
if Team.objects.filter(name=pname, event=event_info).exists():
|
error = _("Invalid characters in name")
|
||||||
error = _("Name already taken.")
|
elif Team.objects.filter(name=pname, event=event_info).exists():
|
||||||
|
error = _("Name already taken.")
|
||||||
ppassword = p_form.cleaned_data['password']
|
ppassword = p_form.cleaned_data['password']
|
||||||
if error is None:
|
if error is None:
|
||||||
p_form.save()
|
p_form.save()
|
||||||
|
@ -135,19 +141,21 @@ def leave_team(request, event_slug):
|
||||||
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
||||||
team = Team.objects.get(event=event_info, name=player.team.name)
|
team = Team.objects.get(event=event_info, name=player.team.name)
|
||||||
|
|
||||||
team.score -= player.score
|
|
||||||
team.save()
|
|
||||||
player.team = None
|
player.team = None
|
||||||
|
player.save()
|
||||||
|
members = EventPlayer.objects.filter(team=team, event=event_info)
|
||||||
|
if members.count() == 0:
|
||||||
|
team.delete()
|
||||||
|
else:
|
||||||
|
team.score -= player.score
|
||||||
|
team.save()
|
||||||
|
|
||||||
solved = CTF_flags.objects.filter(user=player.user, ctf__event=event_info)
|
solved = CTF_flags.objects.filter(user=player.user, ctf__event=event_info)
|
||||||
player.score = 0
|
player.score = 0
|
||||||
solved.delete()
|
solved.delete()
|
||||||
player.save()
|
player.save()
|
||||||
|
|
||||||
members = EventPlayer.objects.filter(team=team, event=event_info)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
if members.count() == 0:
|
|
||||||
team.delete()
|
|
||||||
|
|
||||||
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def find_team(request, event_slug):
|
def find_team(request, event_slug):
|
||||||
|
@ -173,4 +181,28 @@ def find_team(request, event_slug):
|
||||||
player.team = team
|
player.team = team
|
||||||
player.save()
|
player.save()
|
||||||
|
|
||||||
return redirect('events:event_info', event_slug=event_slug)
|
return redirect('events:event_info', event_slug=event_slug)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def open_team(request, event_slug):
|
||||||
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
|
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
||||||
|
|
||||||
|
if not player.team:
|
||||||
|
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
||||||
|
|
||||||
|
player.team.auto = True
|
||||||
|
player.team.save()
|
||||||
|
return redirect('events:manage_team', event_slug=event_slug)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def close_team(request, event_slug):
|
||||||
|
event_info = get_object_or_404(Event, slug=event_slug)
|
||||||
|
player = EventPlayer.objects.get(user=request.user, event=event_info)
|
||||||
|
|
||||||
|
if not player.team:
|
||||||
|
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
|
||||||
|
|
||||||
|
player.team.auto = False
|
||||||
|
player.team.save()
|
||||||
|
return redirect('events:manage_team', event_slug=event_slug)
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django.contrib import sitemaps
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
class StaticViewSitemap(sitemaps.Sitemap):
|
||||||
|
priority = 0.5
|
||||||
|
changefreq = 'monthly'
|
||||||
|
i18n = True
|
||||||
|
def items(self):
|
||||||
|
return ['home', 'cgu']
|
||||||
|
|
||||||
|
def location(self, item):
|
||||||
|
return reverse(item)
|
|
@ -4,7 +4,7 @@
|
||||||
{% get_current_language as lang %}
|
{% get_current_language as lang %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 news-card">
|
<div class="col-12 news-card">
|
||||||
<h2>Conditions générales d'utilisation du site 42CTF</h2>
|
<h1>Conditions générales d'utilisation du site 42CTF</h1>
|
||||||
|
|
||||||
<h5>Article 1 : Objet</h5>
|
<h5>Article 1 : Objet</h5>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% get_current_language as lang %}
|
{% get_current_language as lang %}
|
||||||
{% load is_member %}
|
{% load is_member %}
|
||||||
|
{% load get_news %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-sm-12 right-sidebar middle-sm">
|
<div class="col-lg-3 col-sm-12 right-sidebar middle-sm">
|
||||||
|
|
||||||
|
@ -34,17 +35,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{ n.name|safe }}</h5>
|
<h5 class="card-title">{{ n.name|safe }}</h5>
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
{% if lang == "fr" and n.content %}
|
{% get_news_by_lang n lang as content %}
|
||||||
{{ n.content|safe }}
|
{{ content | safe }}
|
||||||
{% elif lang == "en" and n.content_en %}
|
|
||||||
{{ n.content_en|safe }}
|
|
||||||
{% elif lang == "de" and n.content_de %}
|
|
||||||
{{ n.content_de|safe }}
|
|
||||||
{% elif lang == "ru" and n.content_ru %}
|
|
||||||
{{ n.content_ru|safe }}
|
|
||||||
{% else %}
|
|
||||||
{% trans "No translation available. Please try another language (English or French)." %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-muted">
|
<div class="card-footer text-muted">
|
||||||
|
@ -83,13 +75,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="row flex-nowrap">
|
<div class="row flex-nowrap">
|
||||||
<div class="col-lg-6 col-sm-3">
|
<div class="col-lg-6 col-md-6">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item active">{% trans "Flags" %}</li>
|
<li class="list-group-item active">{% trans "Flags" %}</li>
|
||||||
<li class="list-group-item"><span>{{ flags }}</span></li>
|
<li class="list-group-item"><span>{{ flags }}</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 col-sm-3">
|
<div class="col-lg-6 col-md-6">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item active">{% trans "Users" %}</li>
|
<li class="list-group-item active">{% trans "Users" %}</li>
|
||||||
<li class="list-group-item"><span>{{ nb_users }}</span></li>
|
<li class="list-group-item"><span>{{ nb_users }}</span></li>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wie Sie es vielleicht schon wissen, braucht man um CTF Herausforderungen zu lösen viele Werkzeuge und es ist manchmal schwierig zu wissen welche benötigt werden.</br>
|
||||||
|
Wir haben eine VM erstellt, mit vorinstallierten Werkzeugen, damit Sie sich auf das wesentliche konzentrieren können: Flaggen!</br>
|
||||||
|
Alles was Sie tun müssen ist diese <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> herunterzuladen und auf <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b> zu importieren.<br>
|
||||||
|
Also, worauf warten Sie?
|
|
@ -0,0 +1,7 @@
|
||||||
|
Haben Sie eine Änderung am Punktestand bemerkt?<br><br>
|
||||||
|
|
||||||
|
Keine Panik, alle Ihre Flaggen sind in Sicherheit. Wir haben bloß zu dynamischen Belohnungen gewächselt. Das heißt, dass die Punktzahl der Herausforderungen nicht mehr festgelegt ist: sie sinken nun jedes Mal, dass die Herausforderung gelöst wird.
|
||||||
|
|
||||||
|
Belohnungspunkte beginnen bei 200 und können nicht unter 5 fallen.<br><br>
|
||||||
|
|
||||||
|
Wir erhoffen, dass dadurch die echte Schwierigkeit der Herausforderungen besser gespiegelt werden kann. Zeitgebundene Ereignisse sind von dieser Änderung nicht beinträchtigt.
|
|
@ -0,0 +1,9 @@
|
||||||
|
Suchen Sie Ihren Seelenverwandten, einen neuen Freund oder bloß einen dezenten CTF-Partner?<br><br>
|
||||||
|
|
||||||
|
Wir bei 42CTF haben was Sie brauchen: den <a href="/events/speed_dating_2022">Speed Dating CTF</a>!<br><br>
|
||||||
|
|
||||||
|
Kommen Sie alleine oder gut begleitet zu diesem sehr kurzen Wettbewerb, der nur 4 Studen dauern wird.<br>
|
||||||
|
Für dieses Team-CTF können Sie nur auf einen anderen Spieler zählen.<br>
|
||||||
|
Sie können entweder Ihren Partner aussuchen oder das Schicksal für sie entscheiden lassen.<br><br>
|
||||||
|
|
||||||
|
Viel Glück!
|
|
@ -0,0 +1,8 @@
|
||||||
|
Schon immer lust gehabt etwas über SQL-Einbrüche zu lernen?<br>
|
||||||
|
<br>
|
||||||
|
Wir bieten Ihnen drei brandneue Herausvorderungen erstellt von <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 Punkte)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 Punkte)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 Punkte)<br>
|
||||||
|
<br>
|
||||||
|
Vergessen Sie nicht, dass Sie uns jederzeit auf <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> erreichen können, um uns neue Herausforderungen vorzuschalgen!
|
|
@ -0,0 +1,9 @@
|
||||||
|
Neues zeitgebundenes Ereignis: <b>Welcome CTF 2021</b>!<br><br>
|
||||||
|
|
||||||
|
Datum: vom 10.12.2021 20 Uhr biz zum 12.12.2021 20 Uhr (Pariser Zeit).<br>
|
||||||
|
Es ist ein CTF um die Studenten die neulich 42 beigetreten haben zu begrüßen.<br>
|
||||||
|
Es wird nur für die Personen die ihren Kursus <b>nach</b> dem 01.09.2021 begonnen haben zugänglich sein.<br><br>
|
||||||
|
|
||||||
|
Ansonsten können Sie trotzdem die auf der Webseite bereits verfügbaren Herausforderungen lösen und versuchen das Top 10 zu erreichen!<br><br>
|
||||||
|
|
||||||
|
Registrieren Sie sich <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>hier</a>.
|
|
@ -0,0 +1,4 @@
|
||||||
|
As you may already be aware, solving CTF challenges require a lot of tools and it may be difficult to find out which ones you need to install. </br>
|
||||||
|
We've made a VM with everything you need for 42CTF challenges so that you can focus on what is really important: flag ! </br>
|
||||||
|
All you need to do is download this <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> and import it in <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>.<br></b>
|
||||||
|
So, what are you waiting for ?
|
|
@ -0,0 +1,7 @@
|
||||||
|
You noticed a little change in the 42CTF scoreboard ?<br><br>
|
||||||
|
|
||||||
|
Don't panic, all your flags are safe and sound. We just switched to dynamic scoring. It means that challenges points are not fixed anymore: they will now decrease with each solve.<br>
|
||||||
|
|
||||||
|
Challenges points start at 200, and cannot go below than 5.<br><br>
|
||||||
|
|
||||||
|
We hope that this will allow better reflecting of the actual difficulty of the challenges. Limited-time events are not affected by this change.
|
|
@ -0,0 +1,3 @@
|
||||||
|
We're pleased to announce that 42CTF source code is now available on a self-hosted gitea <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30" alt="Gitea logo"></a><br><br>
|
||||||
|
|
||||||
|
If you want to contribute to the platform (development or translation), you can send us a message on <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Discord logo"></a> or simply fill this <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">form</a> and we'll contact you !
|
|
@ -0,0 +1,9 @@
|
||||||
|
Looking for your soul mate, a new friend, or just a decent CTF companion ?<br><br>
|
||||||
|
|
||||||
|
We at 42CTF have what you need: the <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
|
||||||
|
|
||||||
|
Come alone or in good company for this very short competition, which will last only 4 hours.<br>
|
||||||
|
You will be able to count on only one other player for this team CTF.<br>
|
||||||
|
You can either choose your partner or you can let the fate decide for you.<br><br>
|
||||||
|
|
||||||
|
Good luck !
|
|
@ -0,0 +1,8 @@
|
||||||
|
Always wanted to learn about SQL injections ? <br>
|
||||||
|
<br>
|
||||||
|
We offer you three brand new challenges created by <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
|
||||||
|
<br>
|
||||||
|
Don't forget that you can always reach out on <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Discord logo"></a> to propose new challenges !
|
|
@ -0,0 +1,9 @@
|
||||||
|
New limited-time event : <b>Welcome CTF 2021</b> !<br><br>
|
||||||
|
|
||||||
|
Dates: from 10/12/2021 8pm to 12/12/2021 8pm (Paris time).<br>
|
||||||
|
This is a CTF to welcome the new students who join us at 42.<br>
|
||||||
|
It will be accessible only for the persons who started their cursus <b>after</b> the 01/09/2021.<br><br>
|
||||||
|
|
||||||
|
For the others, you can still solve the challenges already available on the website and try to reach the top 10 ! <br><br>
|
||||||
|
|
||||||
|
Registration <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>here</a>
|
|
@ -0,0 +1,4 @@
|
||||||
|
Como ya sabrás, resolver retos CTF requiere un montón de herramientas y puede ser dificil encontrar cuales tienes que instalar. </br>
|
||||||
|
Hemos hecho una máquina virtual con todo lo que necesitas para resolver para resolver retos de 42CTF, para que te puedas concentrar en lo importante: Flags! </br>
|
||||||
|
Todo lo que necesitas es descargar esto <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> e importarlo en <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>.<br></b>
|
||||||
|
¿A qué estás esperando?
|
|
@ -0,0 +1,7 @@
|
||||||
|
¿ Has notado un pequeño cambio en la Tabla de Puntos de 42CTF ?<br><br>
|
||||||
|
|
||||||
|
No entres en pánico, todas tus flags estan a salvo. Solo hemos cambiado a puntuación dinámica. Esto significa que los puntos de retos no son fijos: irán disminuyendo cada vez que son resueltos.<br>
|
||||||
|
|
||||||
|
Los puntos de retos empiezan en 200, y no pueden valer menos de 5 puntos. <br><br>
|
||||||
|
|
||||||
|
Esperamos que esto ayude a reflejar la dificultad del reto. Eventos de tiempo limitado nos e ven afectados por este cambio.
|
|
@ -0,0 +1,9 @@
|
||||||
|
¿ Estás buscando tu alma gemela ? ¿ Un nuevo amigo ? ¿ O simplemente un buen compañero de CTF ?<br><br>
|
||||||
|
|
||||||
|
Aquí en 42CTF tenemos lo que necesitas: el <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
|
||||||
|
|
||||||
|
Ven solo o en compañía a esta competición cortita, que solo durará 4 horas.<br>
|
||||||
|
Podrás contar con un solo compañero para este CTF por equipos.<br>
|
||||||
|
Puedes o elegir un compañero o dejar a la fortuna que elija por ti.<br><br>
|
||||||
|
|
||||||
|
¡ Buena suerte !
|
|
@ -0,0 +1,8 @@
|
||||||
|
¿ Siempre has querido aprender sobre SQL injection ? <br>
|
||||||
|
<br>
|
||||||
|
Te ofrecemos tres nuevos retos creados por <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Cuestión de lógica simple 1</a></b> (10 puntos)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Cuestión de lógica simple 2</a></b> (30 puntos)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Cuestión de lógica simple 3</a></b> (40 puntos)<br>
|
||||||
|
<br>
|
||||||
|
No te olvides que siempre puedes contactarnos en <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> para proponer nuevos retos !
|
|
@ -0,0 +1,9 @@
|
||||||
|
Nuevo evento de tiempo limitado: <b>Bienvenida a CTF 2021</b> !<br><br>
|
||||||
|
|
||||||
|
Fechas: desde 10/12/2021 8pm a 12/12/2021 8pm (Hora parís).<br>
|
||||||
|
Este CTF es una bienvenida para los nuevos estudiantes que se unan a 42.<br>
|
||||||
|
Solo estará disponible para las personas que empezaron su cursus <b>después</b> de 01/09/2021.<br><br>
|
||||||
|
|
||||||
|
Para otros, puedes resolver otros retos ya disponibles e intentar llegar al top 10! <br><br>
|
||||||
|
|
||||||
|
Registro: <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>aquí</a>
|
|
@ -0,0 +1,7 @@
|
||||||
|
Comme vous le savez surement déjà, résoudre des challenges nécessite beaucoup d'outils et il peut être difficile de savoir lesquels sont vraiment nécessaires. <br>
|
||||||
|
|
||||||
|
Nous avons créé une VM avec tout ce dont vous avez besoin pour 42CTF afin que vous vous concentriez sur ce qui compte vraiment : flag ! <br>
|
||||||
|
|
||||||
|
Tout ce dont vous avez besoin est de télécharger cet <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> et de l'importer dans <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b>.<br>
|
||||||
|
|
||||||
|
Alors, qu'est ce que vous attendez ?
|
|
@ -0,0 +1,7 @@
|
||||||
|
Vous avez remarqué un petit changement sur le scoreboard de 42CTF ?<br><br>
|
||||||
|
|
||||||
|
Pas de panique, tous vos flags sont sains et saufs. Nous avons juste basculé sur du scoring dynamique. Cela signifie que les points des challenges ne sont plus fixes : ils diminueront désormais à chaque nouvelle résolution.<br>
|
||||||
|
|
||||||
|
Un challenge démarre à 200 points, et ne peut pas descendre en dessous de 5 points.<br><br>
|
||||||
|
|
||||||
|
Nous espérons que cela permettra de mieux refléter la véritable difficulté des challenges. Les événements à durée limitée ne sont pas concernés par ce changement.
|
|
@ -0,0 +1,3 @@
|
||||||
|
Nous sommes heureux de vous annoncer que le code source de 42CTF est désormais disponible sur un <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30" alt="Logo Gitea"></a> auto-hébergé.<br><br>
|
||||||
|
|
||||||
|
Si vous voulez contribuer a la plateforme (développement ou traduction), vous pouvez nous envoyer un message sur <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Logo Discord"></a> ou simplement remplir ce <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">formulaire</a> et nous vous contacterons !
|
|
@ -0,0 +1,9 @@
|
||||||
|
À la recherche de l'âme soeur, d'un nouvel ami, ou juste d'un compagnon de CTF ?<br><br>
|
||||||
|
|
||||||
|
Nous avons ce dont vous avez besoin : le <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
|
||||||
|
|
||||||
|
Venez seul ou bien accompagné pour cette très courte compétition, qui ne durera que 4 heures.<br>
|
||||||
|
Vous ne pourrez compter que sur une seule autre personne pour ce CTF en équipe.<br>
|
||||||
|
Vous pouvez soit choisir votre partenaire, soit laisser laisser le destin décider pour vous.<br><br>
|
||||||
|
|
||||||
|
Bonne chance !
|
|
@ -0,0 +1,8 @@
|
||||||
|
Vous avez toujours voulu en apprendre plus sur les injections SQL ? <br>
|
||||||
|
<br>
|
||||||
|
On vous propose trois nouveaux challenges créés par <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
|
||||||
|
<br>
|
||||||
|
N'oubliez pas que vous pouvez toujours nous contacter sur <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Logo Discord"></a> pour proposer des nouveaux challenges !
|
|
@ -0,0 +1,9 @@
|
||||||
|
Nouvel évènement en temps limité : <b>Welcome CTF 2021</b> !<br><br>
|
||||||
|
|
||||||
|
Dates : du 10/12/2021 20h au 12/12/2021 20h. <br>
|
||||||
|
Il s'agit d'un CTF pour souhaiter la bienvenue aux nouveaux étudiants qui nous rejoignent à 42.<br>
|
||||||
|
Il ne sera donc accessible qu'aux personnes ayant effectué leur rentrée <b>après</b> le 01/09/2021.<br><br>
|
||||||
|
|
||||||
|
Pour les autres, vous pouvez toujours résoudre les challenges déjà disponibles sur le site et tenter de vous hisser dans le top 10 ! <br><br>
|
||||||
|
|
||||||
|
Inscriptions <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>ici</a>
|
|
@ -0,0 +1,4 @@
|
||||||
|
すでにお気づきかもしれませんが、CTFの課題を解くには多くのツールが必要で、どれをインストールすれば良いのかがわかりにくいかもしれません。</br>
|
||||||
|
私たちは、あなたが本当に重要なことに集中できるように、42CTFの課題に必要な全てのツールを備えたVMを作成しました。重要なのはフラグです!</br>
|
||||||
|
この<b><a href="/media/xubuntu-42ctf.ova">OVA</a></b>をダウンロードし、<b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b>にインポートするだけです。<br>
|
||||||
|
さて、何をためらっているのですか?
|
|
@ -0,0 +1,7 @@
|
||||||
|
42CTFのスコアボードにちょっとした変化があったことにお気づきですか?<br><br>
|
||||||
|
|
||||||
|
慌てないでください、 あなたのフラグはすべて無事です。 動的スコアリングに切り替えただけです。 それはチャレンジポイントがもう固定ではないことを意味します。解決するたびに減少します。<br>
|
||||||
|
|
||||||
|
チャレンジポイントは200から始まり、5より低くなることはありません。<br><br>
|
||||||
|
|
||||||
|
これにより、課題の実際の難易度をより良く反映できるようになると期待しています。期間限定イベントは、この変更の影響は受けません。
|
|
@ -0,0 +1,3 @@
|
||||||
|
42CTFのソースコードがセルフホストのgitea <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30"></a> で公開されたことをお知らせします。<br><br>
|
||||||
|
|
||||||
|
プラットフォームへの貢献(開発や翻訳)をしていただける方は、 <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> にメッセージをお送りいただくか、こちらの <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">フォーム</a> にご記入いただければご連絡いたします!
|
|
@ -0,0 +1,9 @@
|
||||||
|
ソウルメイト、新しい友達、またはちょうど良いCTFの仲間を探していますか?<br><br>
|
||||||
|
|
||||||
|
私たち42CTFは、あなたが求めているものを持っています。それは<a href="/events/speed_dating_2022">Speed Dating CTF</a>です!<br><br>
|
||||||
|
|
||||||
|
4時間という短い時間ですが、お一人でも、お仲間とご一緒でも、ぜひご参加ください。<br>
|
||||||
|
このチーム戦CTFでは、自分以外の一人のプレーヤーのみ頼ることができます。<br>
|
||||||
|
相手を選ぶもよし、運命に身を任せるもよし。<br><br>
|
||||||
|
|
||||||
|
幸運を祈ります!
|
|
@ -0,0 +1,8 @@
|
||||||
|
SQLインジェクションについて学びたいと思ったことはありませんか?<br>
|
||||||
|
<br>
|
||||||
|
<b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>が作成した全く新しい3つの課題を提供します。<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
|
||||||
|
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
|
||||||
|
<br>
|
||||||
|
新しい課題を提案するために、<a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a>へいつでも連絡できることを忘れないでください!
|
|
@ -0,0 +1,9 @@
|
||||||
|
期間限定の新イベント:<b>Welcome CTF 2021</b>!<br><br>
|
||||||
|
|
||||||
|
日程:2021年12月10日20時~2021年12月12日20時(パリ時間)<br>
|
||||||
|
42に入学する新入生を歓迎するためのCTFです。<br>
|
||||||
|
2021年9月1日<b>以降</b>にカーサスを開始した方のみアクセス可能です。<br><br>
|
||||||
|
|
||||||
|
それ以外の方は、ウェブサイト上で公開されている課題を解いて、トップ10入りを目指してください!<br><br>
|
||||||
|
|
||||||
|
登録は<a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>こちら</a>
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django import template
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def get_news_by_lang(news, lang):
|
||||||
|
filepath = "home/templates/news/"+ lang + "/" + news.slug + ".html"
|
||||||
|
try:
|
||||||
|
with open(filepath) as fp:
|
||||||
|
return fp.read()
|
||||||
|
except:
|
||||||
|
return news.content_en
|
|
@ -1,7 +1,14 @@
|
||||||
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from .sitemaps import StaticViewSitemap
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
sitemaps = {
|
||||||
|
'static': StaticViewSitemap(),
|
||||||
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.home, name='home'),
|
path('', views.home, name='home'),
|
||||||
path('CGU', views.cgu, name='cgu'),
|
path('CGU', views.cgu, name='cgu'),
|
||||||
|
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,26 +8,17 @@ from django.urls import translate_url
|
||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
LANGUAGE_SESSION_KEY, check_for_language, get_language,
|
LANGUAGE_SESSION_KEY, check_for_language, get_language,
|
||||||
)
|
)
|
||||||
import datetime
|
from django.core.files.storage import default_storage
|
||||||
|
|
||||||
|
# import datetime
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
def get_content_by_lang(news):
|
|
||||||
lang = get_language()
|
|
||||||
ret = None
|
|
||||||
if lang == "fr":
|
|
||||||
ret = news.content
|
|
||||||
elif lang == "en":
|
|
||||||
ret = news.content_en
|
|
||||||
elif lang == "de":
|
|
||||||
ret = news.content_de
|
|
||||||
elif lang == "ru":
|
|
||||||
ret = news.content_ru
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def get_weekly_top():
|
def get_weekly_top():
|
||||||
week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
|
week_ago = timezone.now() - timezone.timedelta(days=7)
|
||||||
weekly_flags = CTF_flags.objects.filter(flag_date__gt=week_ago)
|
weekly_flags = CTF_flags.objects.filter(flag_date__gt=week_ago, ctf__disabled=False, ctf__event=None)
|
||||||
scores = defaultdict(int)
|
scores = defaultdict(int)
|
||||||
|
|
||||||
for sol in weekly_flags:
|
for sol in weekly_flags:
|
||||||
|
@ -44,15 +35,14 @@ def home(request):
|
||||||
lang_code = request.session[LANGUAGE_SESSION_KEY]
|
lang_code = request.session[LANGUAGE_SESSION_KEY]
|
||||||
url_translated = translate_url(request.path, lang_code)
|
url_translated = translate_url(request.path, lang_code)
|
||||||
if request.path != url_translated:
|
if request.path != url_translated:
|
||||||
print("%s\n%s" % (request.path, url_translated))
|
|
||||||
response = HttpResponseRedirect(url_translated)
|
response = HttpResponseRedirect(url_translated)
|
||||||
return response
|
return response
|
||||||
news = new.objects.order_by('-pub_date')[:5]
|
news = new.objects.order_by('-pub_date')[:5]
|
||||||
latest_ctfs = CTF.objects.filter(event=None).order_by('-pub_date')[:5]
|
latest_ctfs = CTF.objects.filter(event=None, disabled=False).order_by('-pub_date')[:5]
|
||||||
top10 = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')[:10]
|
top10 = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')[:10]
|
||||||
nb_flags = CTF_flags.objects.count()
|
nb_flags = CTF_flags.objects.count()
|
||||||
nb_users = UserProfileInfo.objects.count()
|
nb_users = UserProfileInfo.objects.count()
|
||||||
latest_flags = CTF_flags.objects.order_by('-flag_date')[:5]
|
latest_flags = CTF_flags.objects.filter(ctf__event = None, ctf__disabled=False).order_by('-flag_date')[:5]
|
||||||
top_weekly = get_weekly_top()
|
top_weekly = get_weekly_top()
|
||||||
|
|
||||||
return render(request, 'home/home.html', {'news' : news, 'ctfs' : latest_ctfs, 'top' : top10, 'flags' : nb_flags,
|
return render(request, 'home/home.html', {'news' : news, 'ctfs' : latest_ctfs, 'top' : top10, 'flags' : nb_flags,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue