Commit 3ec73d39 authored by Julien Palard's avatar Julien Palard
Browse files

Move to a custom user model.

Following: https://code.djangoproject.com/ticket/25313#comment:18
parent eca0492c
......@@ -282,6 +282,8 @@ ALLOWED_STYLES = []
GIT_HEAD = "master" # Changed in production to the current commit hash, can be used for static file invalidation.
AUTH_USER_MODEL = "website.User"
try:
from local_settings import *
except ImportError:
......
......@@ -4,15 +4,14 @@ import json
import pytz
import django
from tqdm import tqdm
from datetime import datetime, timezone
from website.models import Exercise, Answer
from django.contrib.auth.models import User
from datetime import datetime
from website.models import Exercise, Answer, User
import random
UNUSABLE_PASSWORD_PREFIX = "!" # This will never be a valid encoded hash
UNUSABLE_PASSWORD_SUFFIX_LENGTH = (
40
) # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
40 # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
)
def get_random_string(
......
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.safestring import mark_safe
from django_ace import AceWidget
from modeltranslation.admin import TranslationAdmin
from website.models import Answer, Exercise, Snippet
from website.models import Answer, Exercise, Snippet, User
class AdminExerciseForm(forms.ModelForm):
......@@ -146,6 +145,4 @@ class MyUserAdmin(UserAdmin):
admin.site.register(Answer, AnswerAdmin)
admin.site.register(Exercise, ExerciseAdmin)
admin.site.register(Snippet, SnippetAdmin)
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
import django_filters
from asgiref.sync import async_to_sync
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Group
from django_filters import rest_framework as filters
from channels.layers import get_channel_layer
from rest_framework import permissions
from rest_framework import routers
from rest_framework import serializers
from rest_framework import viewsets
from website.models import Answer, Exercise, Snippet
from website.models import Answer, Exercise, Snippet, User
class AdminOrReadOnly(permissions.BasePermission):
......@@ -27,8 +27,7 @@ class AnswerPermission(permissions.BasePermission):
"""
def has_permission(self, request, view):
"""Authenticated users can create (POST) but not edit.
"""
"""Authenticated users can create (POST) but not edit."""
if not request.user.is_authenticated:
return False
if request.user.is_staff:
......@@ -49,8 +48,7 @@ class AnswerPermission(permissions.BasePermission):
return False
def has_object_permission(self, request, view, obj):
"""Users can only see and modify own answers
"""
"""Users can only see and modify own answers"""
if request.user.is_staff:
return True
elif request.user.is_authenticated and obj.user == request.user:
......
......@@ -4,11 +4,10 @@ import logging
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.contrib.auth.models import User
from django.utils.timezone import now
from moulinette.tasks import check_answer, run_snippet
from website.models import Answer, Exercise, Snippet, UserStats
from website.models import Answer, Exercise, Snippet, UserStats, User
from website.utils import markdown_to_bootstrap
from website.serializers import AnswerSerializer, SnippetSerializer
......
# Generated by Django 2.0.1 on 2018-01-28 09:53
# Generated by Django 3.1.3 on 2020-12-04 10:35
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_extensions.db.fields
class Migration(migrations.Migration):
initial = True
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name="Answer",
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'db_table': 'auth_user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='UserStats',
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("source_code", models.TextField()),
("is_corrected", models.BooleanField(default=False)),
("is_valid", models.BooleanField(default=False)),
("correction_message", models.TextField(blank=True, default="")),
("created_at", models.DateTimeField(auto_now_add=True)),
("corrected_at", models.DateTimeField(blank=True, null=True)),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('points', models.FloatField(default=0)),
('rank', models.PositiveIntegerField(blank=True, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name="Exercise",
name='Snippet',
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=255)),
("check", models.TextField()),
("solution", models.TextField()),
("wording", models.TextField()),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('source_code', models.TextField()),
('output', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('executed_at', models.DateTimeField(blank=True, null=True)),
('user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name="answer",
name="exercise",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="answers",
to="website.Exercise",
),
migrations.CreateModel(
name='Exercise',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('title_en', models.CharField(max_length=255, null=True)),
('title_fr', models.CharField(max_length=255, null=True)),
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['title'])),
('pre_check', models.TextField(blank=True, null=True)),
('check', models.TextField()),
('is_published', models.BooleanField(default=False)),
('solution', models.TextField()),
('wording', models.TextField()),
('wording_en', models.TextField(null=True)),
('wording_fr', models.TextField(null=True)),
('initial_solution', models.TextField(blank=True)),
('position', models.FloatField(default=0)),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['position'],
},
),
migrations.AddField(
model_name="answer",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
migrations.CreateModel(
name='Answer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('source_code', models.TextField()),
('is_corrected', models.BooleanField(default=False)),
('is_valid', models.BooleanField(default=False)),
('is_shared', models.BooleanField(default=False)),
('correction_message', models.TextField(blank=True, default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('corrected_at', models.DateTimeField(blank=True, null=True)),
('is_unhelpfull', models.BooleanField(blank=True, default=False)),
('exercise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='website.exercise')),
('user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
# Generated by Django 2.1.3 on 2018-11-25 15:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('website', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='exercise',
options={'ordering': ['id']},
),
]
# Generated by Django 2.1.3 on 2018-11-28 23:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0002_auto_20181125_1536'),
]
operations = [
migrations.AlterModelOptions(
name='exercise',
options={'ordering': ['position']},
),
migrations.AddField(
model_name='exercise',
name='position',
field=models.FloatField(default=0),
),
]
# Generated by Django 2.1.3 on 2018-11-29 08:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0003_auto_20181128_2306'),
]
operations = [
migrations.AddField(
model_name='exercise',
name='initial_solution',
field=models.TextField(default='#!/usr/bin/env python3\n\n'),
),
]
# Generated by Django 2.1.3 on 2018-11-29 09:43
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('website', '0004_exercise_initial_solution'),
]
operations = [
migrations.CreateModel(
name='Snippet',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('source_code', models.TextField()),
('output', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('executed_at', models.DateTimeField(blank=True, null=True)),
('user', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
# Generated by Django 2.1 on 2018-12-02 10:50
from django.db import migrations
import django_extensions.db.fields
class Migration(migrations.Migration):
dependencies = [
('website', '0005_snippet'),
]
operations = [
migrations.AddField(
model_name='exercise',
name='slug',
field=django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['title']),
),
]
# Generated by Django 2.1 on 2019-01-18 21:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("website", "0006_exercise_slug")]
operations = [
migrations.AddField(
model_name="exercise",
name="is_published",
field=models.BooleanField(default=False),
)
]
# Generated by Django 2.1 on 2019-01-18 21:51
from django.db import migrations
def publish_existing(apps, schema_editor):
Exercise = apps.get_model("website", "Exercise")
for exercise in Exercise.objects.all():
exercise.is_published = True
exercise.save()
class Migration(migrations.Migration):
dependencies = [("website", "0007_exercise_is_published")]
operations = [migrations.RunPython(publish_existing)]
# Generated by Django 2.1 on 2019-01-25 18:46
from django.db import migrations, models
import django_extensions.db.fields
class Migration(migrations.Migration):
dependencies = [
('website', '0008_auto_20190118_2151'),
]
operations = [
migrations.CreateModel(
name='Lesson',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['title'])),
('content', models.TextField()),
('is_published', models.BooleanField(default=False)),
('position', models.FloatField(default=0)),
],
options={
'ordering': ['position'],
},
),
]
# Generated by Django 2.1 on 2019-02-11 21:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0009_lesson'),
]
operations = [
migrations.AddField(
model_name='answer',
name='is_shared',
field=models.BooleanField(default=False),
),
]
# Generated by Django 3.0.8 on 2020-07-21 14:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("website", "0010_answer_is_shared"),
]
operations = [
migrations.DeleteModel(name="Lesson",),
]
# Generated by Django 3.0.8 on 2020-07-21 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0011_auto_20200721_1628'),
]
operations = [
migrations.AlterField(
model_name='exercise',
name='initial_solution',
field=models.TextField(blank=True),
),
]
# Generated by Django 3.0.8 on 2020-07-21 22:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('website', '0012_auto_20200721_1630'),
]
operations = [
migrations.CreateModel(
name='UserStats',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('points', models.FloatField(default=0)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
# Generated by Django 3.0.8 on 2020-07-21 22:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0013_userstats'),
]
operations = [
migrations.AddField(
model_name='userstats',
name='rank',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
# Generated by Django 3.0.8 on 2020-07-22 07:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('website', '0014_userstats_rank'),
]
operations = [
migrations.AddField(
model_name='exercise',
name='author',
field=models.CharField(blank=True, max_length=255),
),
]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment