diff --git a/.gitignore b/.gitignore index 9f0242823d723af98a6a9c19b349f948fd7f31de..257b15a07d5968520df7f8d2e42835e41ed6d12f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ tests/.coverage build/ tests/report/ db.sqlite3 -env-grady/ \ No newline at end of file +env-grady/ diff --git a/core/__init__.py b/core/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..17acc4cd4393f0d1b62100ecfb5ed2a5db34e814 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -0,0 +1 @@ +default_app_config = 'core.apps.CoreConfig' diff --git a/core/apps.py b/core/apps.py index 26f78a8e673340121f68a92930e2830bc58d269d..089650b78caaed54d112d93f4694d1c73167bdb6 100644 --- a/core/apps.py +++ b/core/apps.py @@ -3,3 +3,4 @@ from django.apps import AppConfig class CoreConfig(AppConfig): name = 'core' + verbose_name = 'where everything comes together' diff --git a/core/fixtures/initial_data.json b/core/fixtures/initial_data.json new file mode 100644 index 0000000000000000000000000000000000000000..87a57244ae962bb23f694b0623d2cc369b2a2eef --- /dev/null +++ b/core/fixtures/initial_data.json @@ -0,0 +1,44 @@ +[ + { + "fields": { + "correct_solution": "Das geht dich gar nichts an.", + "correction_guideline": "Mach das mal so und so.", + "full_score": 10, + "name": "Irgendwas Sortieren" + }, + "model": "core.submissiontype", + "pk": "123" + }, + { + "fields": { + "matrikel_no": 21999999, + "user": 17 + }, + "model": "core.student", + "pk": 1 + }, + { + "fields": { + "final_feedback": null, + "pre_corrections": "Was das System halt so kann.", + "student": 1, + "submission_text": "class Uhrzeit implements Comparable<Uhrzeit> {\r\n private int stunde; // Stundenangabe\r\n private int minute; // Minutenangabe\r\n public Uhrzeit(int stunde, int minute) { // nur sinnvolle Zeitangaben ..\r\n if ( 0 <= stunde && stunde <= 23 && // .. behandeln\r\n 0 <= minute && minute <= 59) {\r\n this.stunde = stunde; // Stundenangabe zuweisen\r\n this.minute = minute; // Minutenangabe zuweisen\r\n } else throw new RuntimeException(\"UngÞltige Zeitangabe!\");\r\n }\r\n\r\n public String toString() { // String-Darstellung in der Form:\r\n return stunde + \" Uhr und \" + minute + \" Minute(n)\"; // <Stundenangabe> Uhr und <Minutenangabe> Minuten\r\n }\r\n}", + "submission_type": "123" + }, + "model": "core.submission", + "pk": 1 + }, + { + "fields": { + "of_reviewer": [ + 16 + ], + "of_submission": 1, + "of_tutor": 15, + "score": 10, + "text": "You did a good job" + }, + "model": "core.feedback", + "pk": 1 + } +] diff --git a/core/fixtures/load_inital.json b/core/fixtures/load_inital.json new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..2e1c0dba53308bd0920a4502880519682b04a3bf --- /dev/null +++ b/core/forms.py @@ -0,0 +1,6 @@ +from django import forms +from core.models import Feedback, Submission + +class CorrectionForm(forms.Form): + comments = forms.TextField() + score = forms.PositiveIntegerField(initial=0) diff --git a/core/migrations/0003_auto_20170303_2053.py b/core/migrations/0003_auto_20170303_2053.py new file mode 100644 index 0000000000000000000000000000000000000000..1419821275dd0c81f1c859a98d4f5323874389dd --- /dev/null +++ b/core/migrations/0003_auto_20170303_2053.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-03 20:53 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0002_auto_20170303_1837'), + ] + + operations = [ + migrations.AlterModelOptions( + name='feedback', + options={'verbose_name': 'Feedback', 'verbose_name_plural': 'Feedback Set'}, + ), + migrations.AlterModelOptions( + name='student', + options={'verbose_name': 'Student', 'verbose_name_plural': 'Students'}, + ), + migrations.AddField( + model_name='submission', + name='submission_text', + field=models.TextField(blank=True), + ), + migrations.RemoveField( + model_name='feedback', + name='of_reviewer', + ), + migrations.AddField( + model_name='feedback', + name='of_reviewer', + field=models.ManyToManyField(related_name='reviewd_submissions', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/core/migrations/0004_submission_final_feedback.py b/core/migrations/0004_submission_final_feedback.py new file mode 100644 index 0000000000000000000000000000000000000000..d606c4772ba3607908c00a38ae5e7626c4300a3c --- /dev/null +++ b/core/migrations/0004_submission_final_feedback.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 09:29 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_auto_20170303_2053'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='final_feedback', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Feedback'), + ), + ] diff --git a/core/migrations/0005_auto_20170304_1200.py b/core/migrations/0005_auto_20170304_1200.py new file mode 100644 index 0000000000000000000000000000000000000000..513af0ba0557afb16a68436f54cc356a6a7c2f37 --- /dev/null +++ b/core/migrations/0005_auto_20170304_1200.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 12:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_submission_final_feedback'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='pre_corrections', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='submission', + name='submission_text', + field=models.TextField(), + ), + ] diff --git a/core/migrations/0006_auto_20170304_1523.py b/core/migrations/0006_auto_20170304_1523.py new file mode 100644 index 0000000000000000000000000000000000000000..ff15887e88d2892764291d2d12b70ebd925a3f3c --- /dev/null +++ b/core/migrations/0006_auto_20170304_1523.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 15:23 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_auto_20170304_1200'), + ] + + operations = [ + migrations.AlterField( + model_name='feedback', + name='of_reviewer', + field=models.ManyToManyField(blank=True, related_name='reviewd_submissions', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/core/migrations/0007_feedback_slug.py b/core/migrations/0007_feedback_slug.py new file mode 100644 index 0000000000000000000000000000000000000000..f0c12cb17e360b6ee18cc69d8b3e5630873ea4dd --- /dev/null +++ b/core/migrations/0007_feedback_slug.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 16:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_auto_20170304_1523'), + ] + + operations = [ + migrations.AddField( + model_name='feedback', + name='slug', + field=models.SlugField(default=0), + ), + ] diff --git a/core/migrations/0008_auto_20170304_1612.py b/core/migrations/0008_auto_20170304_1612.py new file mode 100644 index 0000000000000000000000000000000000000000..c244e8a3affaa9da478db5bbae9f271b685e50b6 --- /dev/null +++ b/core/migrations/0008_auto_20170304_1612.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 16:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0007_feedback_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='feedback', + name='slug', + field=models.SlugField(blank=True), + ), + ] diff --git a/core/migrations/0009_auto_20170304_1614.py b/core/migrations/0009_auto_20170304_1614.py new file mode 100644 index 0000000000000000000000000000000000000000..f998c6653b13f7923d92543616567d8062332020 --- /dev/null +++ b/core/migrations/0009_auto_20170304_1614.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-04 16:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_auto_20170304_1612'), + ] + + operations = [ + migrations.AlterField( + model_name='feedback', + name='slug', + field=models.SlugField(editable=False), + ), + ] diff --git a/core/models.py b/core/models.py index 9fb8e8849728268cc2579f7aaf1d2d068fd30b08..ea22bdb8c3238b9aabd99fbe62482b241af39d22 100644 --- a/core/models.py +++ b/core/models.py @@ -1,9 +1,11 @@ +from random import sample +from string import ascii_lowercase + from django.db import models from django.contrib.auth.models import User +from django.template.defaultfilters import slugify -# Create your models here. - class SubmissionType(models.Model): # Fields @@ -22,19 +24,28 @@ class SubmissionType(models.Model): class Student(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) + + # Fields + user = models.OneToOneField( + User, on_delete=models.CASCADE, limit_choices_to={'groups__name': 'Students'}) matrikel_no = models.PositiveIntegerField(unique=True, default=0) class Meta: verbose_name = "Student" verbose_name_plural = "Students" + def __str__(self): + return "{} ({})".format(self.user.username, self.matrikel_no) + class Submission(models.Model): # Fields - pre_corrections = models.TextField() - submission_type = models.ForeignKey(SubmissionType, related_name='submissions') + submission_type = models.ForeignKey( + SubmissionType, related_name='submissions') + submission_text = models.TextField() + pre_corrections = models.TextField(blank=True) + final_feedback = models.OneToOneField('Feedback', null=True, blank=True) student = models.OneToOneField(Student) class Meta: @@ -42,24 +53,41 @@ class Submission(models.Model): verbose_name_plural = "Submissions" def __str__(self): - pass + return "Submission of type '{}' from Student '{}'".format( + self.submission_type, + self.student + ) class Feedback(models.Model): - # fields + # Fields score = models.PositiveIntegerField(default=0) text = models.TextField() of_submission = models.ForeignKey(Submission) - of_tutor = models.ForeignKey(User, related_name='corrected_submissions') - of_reviewer = models.ForeignKey(User, related_name='reviewd_submissions') + of_tutor = models.ForeignKey( + User, + related_name='corrected_submissions', + limit_choices_to={'groups__name': 'Tutors'} + ) + of_reviewer = models.ManyToManyField( + User, + related_name='reviewd_submissions', + limit_choices_to={'groups__name': 'Reviewers'}, + blank=True + ) + slug = models.SlugField(editable=False) class Meta: verbose_name = "Feedback" verbose_name_plural = "Feedback Set" + def save(self, *args, **kwargs): + self.slug = ''.join(sample(ascii_lowercase, 16)) + super(Feedback, self).save(*args, **kwargs) + def __str__(self): - pass + return 'Feedback for {}'.format(self.of_submission) def is_full_score(self): return of_submission.full_score == score diff --git a/core/templates/core/feedback.html b/core/templates/core/feedback.html new file mode 100644 index 0000000000000000000000000000000000000000..09062b36f14f8c2add8178deafdd08a9fa7142be --- /dev/null +++ b/core/templates/core/feedback.html @@ -0,0 +1,21 @@ +<html> +<head> + <meta charset="UTF-8"> + <title>Feedback View</title> +</head> +<body> + <h1>This is the view for {{ feedback }}</h1> + + <div> + <h2>The student {{ feedback.of_submission.student }} has submitted:</h2> + <pre>{{ feedback.of_submission.submission_text }}</pre> + <h2> {{ feedback.of_tutor }} has created this feedback:</h2> + <pre>{{ feedback.text }}</pre> + <p>The final score based on this feedback is <code>{{ feedback.score }}</code>. </p> + </div> + + {% if not feedback.of_reviwer %} + <h2>Nobody reviewed this feedback so far</h2> + {% endif %} +</body> +</html> diff --git a/core/templates/core/index.html b/core/templates/core/index.html new file mode 100644 index 0000000000000000000000000000000000000000..7832b53516816ac7ea2371d8b8370becddb0ec67 --- /dev/null +++ b/core/templates/core/index.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + + <head> + <title>Rango</title> + </head> + + <body> + <h1>All the types</h1> + + {% for subm in submission_t %} + <h2> {{ subm }}</h2> + <ul> + {% for s in submissions %} + <li> {{ s }} </li> + {% endfor %} + </ul> + {% endfor %} + <strong>{{ boldmessage }}</strong><br> + </body> + +</html> diff --git a/core/templates/core/login.html b/core/templates/core/login.html new file mode 100644 index 0000000000000000000000000000000000000000..81e9b253c184c3b72d3a27bf25e9f88009871aae --- /dev/null +++ b/core/templates/core/login.html @@ -0,0 +1,16 @@ +<html> +<head> + <meta charset="UTF-8"> + <title>Grady -- Login</title> +</head> +<body> + <h1>Welcome to Correction Hell</h1> + + <form id="login_form" method="post" action="/login/"> + {% csrf_token %} + <p>Username: <input type="text" name="username" value="" size="30"></p> + <p>Passwort: <input type="password" name="password" value="" size="30"></p> + <p><input type="submit" value="submit" /></p> + </form> +</body> +</html> diff --git a/core/templates/core/tutor_startpage.html b/core/templates/core/tutor_startpage.html new file mode 100644 index 0000000000000000000000000000000000000000..c79ab959e5d013e6e985a62dda98ecf29338d4ff --- /dev/null +++ b/core/templates/core/tutor_startpage.html @@ -0,0 +1,26 @@ +<html> +<head> + <meta charset="UTF-8"> + <title>Tutor Startpage</title> +</head> +<body> + <h1>Hello {{tutor_name}}! </h1> + + {% if feedback_list|length == 0 %} + <h2>You havn't provided any feedback yet. Sad. Get to work!</h2> + {% else %} + {% if feedback_list|length == 1 %} + <h2>So far you have provieded one constribution </h2> + {% else %} + <h2>So far you have provided {{feedback_list|length}} constributions.</h2> + {% endif %} + <ul> + {% for feedback in feedback_list %} + <li> <a href="/feedback/{{ feedback.slug }}/"> {{ feedback }}</a> </li> + {% endfor %} + </ul> + {% endif %} + + <p><a href="/logout/">Logout</a></p> +</body> +</html> diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..3c162974d5d8c3979f4b5c5957e9b2af94fed605 --- /dev/null +++ b/core/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url +from core import views + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^login/$', views.user_login, name='login'), + url(r'^logout/$', views.user_logout, name='logout'), + url(r'^tutor/$', views.tutor_startpage, name='tutor'), + url(r'^feedback/(?P<feedback_slug>\w+)/$', views.feedback, name='feedback'), +] diff --git a/core/views.py b/core/views.py index 27cdb63bf2c6062ddf98320c2178a012da2b8bfb..fe3da9cb27132943b12d9fb7020af7b548819014 100644 --- a/core/views.py +++ b/core/views.py @@ -1,4 +1,77 @@ +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.models import User, Group +from django.contrib.auth.decorators import user_passes_test, login_required +from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render +from core.models import Submission, SubmissionType, Feedback + + # Create your views here. +def index(request): + context = { + 'boldmessage': 'Delbert Grady says hey there world!', + 'submission_t': SubmissionType.objects.all(), + 'submissions': Submission.objects.all(), + } + return render(request, 'core/index.html', context) + + +def delegate_user(user) -> str: + if user.groups.filter(name='Tutors').exists(): + return '/tutor/' + if user.groups.filter(name='Reviewers').exists(): + return '/reviewers/' + if user.groups.filter(name='Students').exists(): + return '/students/' + + +def user_login(request): + + if request.method == 'POST': + username = request.POST['username'] + password = request.POST['password'] + + user = authenticate(username=username, password=password) + + if user is not None: + if user.is_active: + login(request, user) + return HttpResponseRedirect(delegate_user(user)) + else: + return HttpResponse("Your Grady account is disabled.") + + else: + # Bad login details were provided. So we can't log the user in. + print("Invalid login details: {0}, {1}".format(username, password)) + return HttpResponse("Invalid login details supplied.") + else: + return render(request, 'core/login.html', {}) + + +@login_required +def user_logout(request): + logout(request) + return HttpResponseRedirect('/login/') + + +def is_tutor(user): + group = Group.objects.get(name='Tutors') + return group in user.groups.all() + + +@user_passes_test(is_tutor, login_url='/login/') +def tutor_startpage(request): + context = { + 'tutor_name': request.user.username, + 'feedback_list': Feedback.objects.filter(of_tutor=request.user) + } + return render(request, 'core/tutor_startpage.html', context) + +@user_passes_test(is_tutor, login_url='/login/') +def feedback(request, feedback_slug): + context = {'feedback': Feedback.objects.get(slug=feedback_slug)} + + # Go render the response and return it to the client. + return render(request, 'core/feedback.html', context) diff --git a/grady/settings.py b/grady/settings.py index 552b307ed219ca1f1a02d65b8a832bfcb366ca17..528a47ed99ed50ec5e172e9ade78ced181328833 100644 --- a/grady/settings.py +++ b/grady/settings.py @@ -27,7 +27,6 @@ DEBUG = True ALLOWED_HOSTS = [] - # Application definition INSTALLED_APPS = [ @@ -37,6 +36,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_extensions', 'core', ] @@ -119,3 +119,10 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/' + +FIXTURE_DIRS = ['/core/fixtures/'] + +GRAPH_MODELS = { + 'all_applications': True, + 'group_models': True, +} diff --git a/grady/urls.py b/grady/urls.py index 5f087860fede9591fa373a4a66c2d85de8546007..1ccdb005a411b0e6d85de76dd4a0be99e5402b2d 100644 --- a/grady/urls.py +++ b/grady/urls.py @@ -13,9 +13,10 @@ Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ -from django.conf.urls import url +from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'^', include('core.urls')) ] diff --git a/populatedb.py b/populatedb.py new file mode 100644 index 0000000000000000000000000000000000000000..086d2cc115f480a6eefea9f938d22626f62881db --- /dev/null +++ b/populatedb.py @@ -0,0 +1,54 @@ +import os, random +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'grady.settings') + +import django +django.setup() + +from core.models import Submission, SubmissionType, Student, Feedback +from django.contrib.auth.models import User, Group + + +def populate(): + if User.objects.get(username='doncamillo'): + print("Was already created. Aborting...") + return + + student_group = add_group('Students') + tutor_group = add_group('Tutors') + reviewer_group = add_group('Reviewers') + + add_user('Test_Tutor', tutor_group) + add_user('Test_Reviewer', reviewer_group) + add_student('Test_Student1') + add_student('Test_Student2') + add_student('Test_Student3') + + +def add_submission_type(name, score, solution): + SubmissionType(name=name, score=score) + +def add_student(name): + student_group = Group.objects.get(name='Students') + student_user = add_user(name, student_group) + student = Student(user=student_user, matrikel_no=20000000 + random.randrange(21343)) + student.save() + + +def add_user(username, group, password='password'): + user, _ = User.objects.get_or_create(username=username) + user.set_password(password) + group.user_set.add(user) + print("- Created user {} and added him to group {}".format(user, group)) + user.save() + return user + + +def add_group(group_name): + group, _ = Group.objects.get_or_create(name=group_name) + group.save() + return group + +# Start execution here! +if __name__ == '__main__': + print("Starting population script...") + populate()