12Parker/python-migrations
092
1{"repo": "studentenportal/web", "pull_number": 239, "instance_id": "studentenportal__web-239", "issue_numbers": "", "base_commit": "20ab2fd057725d6a23422c46f0e62cdc5fdc41d9", "patch": "diff --git a/apps/api/urls.py b/apps/api/urls.py\n--- a/apps/api/urls.py\n+++ b/apps/api/urls.py\n@@ -6,6 +6,9 @@\n from apps.api import views\n \n \n+app_name = 'api'\n+\n+\n v1_api = [\n url(r'^$', views.api_root, name='api_root'),\n url(r'^users$', views.UserList.as_view(), name='user_list'),\n@@ -19,6 +22,6 @@\n ]\n \n urlpatterns = [\n- url(r'^', include('rest_framework.urls', namespace='rest_framework')),\n+ url(r'^', include('rest_framework.urls')),\n url(r'^v1/', include(format_suffix_patterns(v1_api))),\n ]\ndiff --git a/apps/documents/migrations/0001_initial.py b/apps/documents/migrations/0001_initial.py\n--- a/apps/documents/migrations/0001_initial.py\n+++ b/apps/documents/migrations/0001_initial.py\n@@ -59,7 +59,7 @@ class Migration(migrations.Migration):\n fields=[\n ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n ('rating', models.PositiveSmallIntegerField(validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(1)])),\n- ('document', models.ForeignKey(related_name='DocumentRating', to='documents.Document')),\n+ ('document', models.ForeignKey(related_name='DocumentRating', to='documents.Document', on_delete=django.db.models.deletion.CASCADE)),\n ],\n ),\n ]\ndiff --git a/apps/documents/migrations/0002_auto_20161124_2111.py b/apps/documents/migrations/0002_auto_20161124_2111.py\n--- a/apps/documents/migrations/0002_auto_20161124_2111.py\n+++ b/apps/documents/migrations/0002_auto_20161124_2111.py\n@@ -18,12 +18,12 @@ class Migration(migrations.Migration):\n migrations.AddField(\n model_name='documentrating',\n name='user',\n- field=models.ForeignKey(related_name='DocumentRating', to=settings.AUTH_USER_MODEL),\n+ field=models.ForeignKey(related_name='DocumentRating', to=settings.AUTH_USER_MODEL, on_delete=django.db.models.deletion.CASCADE),\n ),\n migrations.AddField(\n model_name='documentdownload',\n name='document',\n- field=models.ForeignKey(related_name='DocumentDownload', to='documents.Document'),\n+ field=models.ForeignKey(related_name='DocumentDownload', to='documents.Document', on_delete=django.db.models.deletion.CASCADE),\n ),\n migrations.AddField(\n model_name='documentcategory',\ndiff --git a/apps/documents/models.py b/apps/documents/models.py\n--- a/apps/documents/models.py\n+++ b/apps/documents/models.py\n@@ -41,7 +41,7 @@ def other_count(self):\n excludes = [Document.DTypes.EXAM, Document.DTypes.SUMMARY]\n return self.Document.exclude(dtype__in=excludes).count()\n \n- def __unicode__(self):\n+ def __str__(self):\n return self.name\n \n class Meta:\n@@ -169,7 +169,7 @@ def save(self, *args, **kwargs):\n self.change_date = datetime.now()\n return super(Document, self).save(*args, **kwargs)\n \n- def __unicode__(self):\n+ def __str__(self):\n return self.name\n \n class Meta:\n@@ -179,7 +179,8 @@ class Meta:\n \n class DocumentDownload(models.Model):\n \"\"\"Tracks a download of a document.\"\"\"\n- document = models.ForeignKey(Document, related_name='DocumentDownload', db_index=True)\n+ document = models.ForeignKey(Document, related_name='DocumentDownload', db_index=True,\n+ on_delete=models.CASCADE)\n timestamp = models.DateTimeField(auto_now_add=True, editable=False)\n ip = models.GenericIPAddressField(unpack_ipv4=True, editable=False, db_index=True)\n \n@@ -196,8 +197,10 @@ class DocumentRating(models.Model):\n \n \"\"\"\n RATING_VALIDATORS = [MaxValueValidator(10), MinValueValidator(1)]\n- user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='DocumentRating')\n- document = models.ForeignKey(Document, related_name='DocumentRating')\n+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='DocumentRating',\n+ on_delete=models.CASCADE)\n+ document = models.ForeignKey(Document, related_name='DocumentRating',\n+ on_delete=models.CASCADE)\n rating = models.PositiveSmallIntegerField(validators=RATING_VALIDATORS)\n \n # Custom model validation\n@@ -205,7 +208,7 @@ def clean(self):\n if self.user == self.document.uploader:\n raise ValidationError('A user cannot rate his own uploads.')\n \n- def __unicode__(self):\n+ def __str__(self):\n fmt_args = self.user.username, self.document.name, self.rating\n return 'User %s Document %s Rating %u' % fmt_args\n \ndiff --git a/apps/documents/urls.py b/apps/documents/urls.py\n--- a/apps/documents/urls.py\n+++ b/apps/documents/urls.py\n@@ -8,6 +8,8 @@\n \n admin.autodiscover()\n \n+app_name = 'documents'\n+\n # Dynamic pages\n urlpatterns = [\n url(r'^$', views.DocumentcategoryList.as_view(), name='documentcategory_list'),\ndiff --git a/apps/documents/views.py b/apps/documents/views.py\n--- a/apps/documents/views.py\n+++ b/apps/documents/views.py\n@@ -12,7 +12,7 @@\n from django.contrib import messages\n from django.contrib.syndication.views import Feed\n from django.db.models import Count\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.core.exceptions import ObjectDoesNotExist, ValidationError\n from django.core.mail import send_mail\n from django.http import HttpResponse\n@@ -116,7 +116,7 @@ def get_queryset(self):\n \n def get_context_data(self, **kwargs):\n context = super(DocumentList, self).get_context_data(**kwargs)\n- if self.request.user.is_authenticated():\n+ if self.request.user.is_authenticated:\n ratings = models.DocumentRating.objects.filter(user=self.request.user)\n context['ratings'] = dict([(r.document.pk, r.rating) for r in ratings])\n return context\n@@ -159,7 +159,7 @@ def get(self, request, *args, **kwargs):\n doc = get_object_or_404(models.Document, pk=self.kwargs.get('pk'))\n # If document is an exam or marked as non-public, require login\n if doc.dtype == doc.DTypes.EXAM or doc.public is False:\n- if not self.request.user.is_authenticated():\n+ if not self.request.user.is_authenticated:\n return redirect('%s?next=%s' % (\n reverse('auth_login'),\n reverse('documents:document_list',\n@@ -174,7 +174,7 @@ def get(self, request, *args, **kwargs):\n # Serve file\n filename = unicodedata.normalize('NFKD', doc.original_filename) \\\n .encode('us-ascii', 'ignore')\n- attachment = not filename.lower().endswith('.pdf')\n+ attachment = not filename.lower().endswith(b'.pdf')\n return sendfile(request, doc.document.path,\n attachment=attachment, attachment_filename=filename)\n \n@@ -308,7 +308,7 @@ def dispatch(self, request, *args, **kwargs):\n return super(DocumentReport, self).dispatch(request, *args, **kwargs)\n \n def get_initial(self):\n- if self.request.user.is_authenticated():\n+ if self.request.user.is_authenticated:\n return {\n 'name': self.request.user.name(),\n 'email': self.request.user.email,\n@@ -343,7 +343,7 @@ def document_rating(request, category, pk):\n to update the text after changing a rating via JavaScript.\"\"\"\n if not request.is_ajax():\n return HttpResponseBadRequest('XMLHttpRequest expected.')\n- if not request.user.is_authenticated():\n+ if not request.user.is_authenticated:\n return HttpResponseForbidden('Login required')\n template = 'front/blocks/document_rating_summary.html'\n context = {'doc': get_object_or_404(models.Document, pk=pk, category__name=category)}\ndiff --git a/apps/events/models.py b/apps/events/models.py\n--- a/apps/events/models.py\n+++ b/apps/events/models.py\n@@ -51,5 +51,5 @@ def days_until(self):\n delta = self.start_date - date.today()\n return delta.days if delta.days > 0 else None\n \n- def __unicode__(self):\n+ def __str__(self):\n return '%s %s' % (self.start_date, self.summary)\ndiff --git a/apps/events/urls.py b/apps/events/urls.py\n--- a/apps/events/urls.py\n+++ b/apps/events/urls.py\n@@ -8,6 +8,8 @@\n \n admin.autodiscover()\n \n+app_name = 'events'\n+\n # Dynamic pages\n urlpatterns = [\n url(r'^$', views.EventList.as_view(), name='event_list'),\ndiff --git a/apps/events/views.py b/apps/events/views.py\n--- a/apps/events/views.py\n+++ b/apps/events/views.py\n@@ -3,13 +3,13 @@\n \n import datetime\n from dateutil.relativedelta import relativedelta\n-from urlparse import urlsplit, urlunsplit\n+from urllib.parse import urlsplit, urlunsplit\n \n from django.views.generic import View, TemplateView\n from django.views.generic.detail import DetailView\n from django.views.generic.edit import CreateView, UpdateView, DeleteView\n from django.contrib import messages\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.http import HttpResponse\n from django.http import HttpResponseForbidden\n \ndiff --git a/apps/front/migrations/0003_username_normalization.py b/apps/front/migrations/0003_username_normalization.py\nnew file mode 100644\n--- /dev/null\n+++ b/apps/front/migrations/0003_username_normalization.py\n@@ -0,0 +1,30 @@\n+# -*- coding: utf-8 -*-\n+# Generated by Django 1.11.27 on 2020-01-21 15:28\n+from __future__ import unicode_literals\n+\n+import django.contrib.auth.models\n+import django.contrib.auth.validators\n+from django.db import migrations, models\n+import django.db.models.manager\n+\n+\n+class Migration(migrations.Migration):\n+\n+ dependencies = [\n+ ('front', '0002_auto_20200119_1707'),\n+ ]\n+\n+ operations = [\n+ migrations.AlterModelManagers(\n+ name='user',\n+ managers=[\n+ ('users', django.db.models.manager.Manager()),\n+ ('objects', django.contrib.auth.models.UserManager()),\n+ ],\n+ ),\n+ migrations.AlterField(\n+ model_name='user',\n+ name='username',\n+ field=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'),\n+ ),\n+ ]\ndiff --git a/apps/front/migrations/0004_auto_20200121_1625.py b/apps/front/migrations/0004_auto_20200121_1625.py\nnew file mode 100644\n--- /dev/null\n+++ b/apps/front/migrations/0004_auto_20200121_1625.py\n@@ -0,0 +1,18 @@\n+# Generated by Django 2.2.9 on 2020-01-21 16:25\n+\n+from django.db import migrations, models\n+\n+\n+class Migration(migrations.Migration):\n+\n+ dependencies = [\n+ ('front', '0003_username_normalization'),\n+ ]\n+\n+ operations = [\n+ migrations.AlterField(\n+ model_name='user',\n+ name='last_name',\n+ field=models.CharField(blank=True, max_length=150, verbose_name='last name'),\n+ ),\n+ ]\ndiff --git a/apps/front/models.py b/apps/front/models.py\n--- a/apps/front/models.py\n+++ b/apps/front/models.py\n@@ -2,10 +2,38 @@\n from __future__ import print_function, division, absolute_import, unicode_literals\n \n from django.db import models\n-from django.contrib.auth.models import AbstractUser\n+from django.contrib.auth.models import AbstractUser, BaseUserManager\n from django.utils.safestring import mark_safe\n \n \n+def strip_mail_part(username):\n+ \"\"\"\n+ Users might try to login with their email. To support that\n+ we can simply strip the mail part from the username\n+ \"\"\"\n+ if '@' in username:\n+ return username.split('@')[0]\n+ return username\n+\n+\n+class CustomUserManager(BaseUserManager):\n+\n+ \"\"\"\n+ By default, django.contrib.auth does case _sensitive_ username authentication, which isn't what is\n+ generally expected. By defining a custom user manager, we can compare user\n+ names case insensitively and strip off the email part.\n+\n+ Sources:\n+\n+ https://djangosnippets.org/snippets/1368/\n+ https://code.djangoproject.com/ticket/2273#comment:12\n+ \"\"\"\n+\n+ def get_by_natural_key(self, username):\n+ username = strip_mail_part(username)\n+ return self.get(username__iexact=username)\n+\n+\n class User(AbstractUser):\n \"\"\"The user model.\"\"\"\n twitter = models.CharField('Twitter Benutzername', max_length=24, blank=True)\n@@ -13,9 +41,11 @@ class User(AbstractUser):\n help_text=mark_safe('Falls angegeben, wird bei deinen Zusammenfassungen jeweils ein '\n '<a href=\"https://flattr.com/\">Flattr</a> Button angezeigt.'))\n \n+ users = CustomUserManager()\n+\n def name(self):\n \"\"\"Return either full user first and last name or the username, if no\n further data is found.\"\"\"\n if self.first_name or self.last_name:\n- return ' '.join(filter(None, [self.first_name, self.last_name]))\n+ return ' '.join(part for part in [self.first_name, self.last_name] if part)\n return self.username\ndiff --git a/apps/front/templatetags/tags.py b/apps/front/templatetags/tags.py\n--- a/apps/front/templatetags/tags.py\n+++ b/apps/front/templatetags/tags.py\n@@ -23,13 +23,13 @@ def get_range(value):\n \n Instead of 3 one may use the variable set in the views\n \"\"\"\n- return xrange(value)\n+ return range(value)\n \n \n @register.filter\n def get_range1(value):\n \"\"\"Like get_range, but with an 1 based index.\"\"\"\n- return xrange(1, value + 1)\n+ return range(1, value + 1)\n \n \n @register.filter\ndiff --git a/apps/front/views.py b/apps/front/views.py\n--- a/apps/front/views.py\n+++ b/apps/front/views.py\n@@ -6,7 +6,7 @@\n from django.contrib.auth import get_user_model\n from django.contrib import messages\n from django.db.models import Count\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.views.generic import TemplateView\n from django.views.generic.edit import UpdateView\n from django.views.generic.detail import DetailView\n@@ -53,7 +53,7 @@ def get_context_data(self, **kwargs):\n user = self.get_object()\n context['lecturerratings'] = user.LecturerRating. \\\n values_list('lecturer').distinct().count()\n- if self.request.user.is_authenticated():\n+ if self.request.user.is_authenticated:\n ratings = document_models.DocumentRating.objects.filter(user=user)\n context['ratings'] = dict([(r.document.pk, r.rating) for r in ratings])\n return context\ndiff --git a/apps/lecturers/management/commands/fetch_lecturers.py b/apps/lecturers/management/commands/fetch_lecturers.py\n--- a/apps/lecturers/management/commands/fetch_lecturers.py\n+++ b/apps/lecturers/management/commands/fetch_lecturers.py\n@@ -20,7 +20,7 @@\n def unicode_csv_reader(utf8_data, dialect=csv.excel, **kwargs):\n csv_reader = csv.reader(utf8_data, dialect=dialect, **kwargs)\n for row in csv_reader:\n- yield [unicode(cell, 'latin1') for cell in row]\n+ yield [cell.encode('latin1') for cell in row]\n \n \n class HsrWebsite(object):\n@@ -127,7 +127,7 @@ def handle(self, **options):\n reader = unicode_csv_reader(f, delimiter=b';')\n # The last element added to the list is a little hack, because all\n # rows except the title row have a trailing semicolon.\n- titles = [t.lower().replace(' ', '_') for t in reader.next()] + ['empty']\n+ titles = [t.lower().replace(' ', '_') for t in next(reader)] + ['empty']\n Person = namedtuple('Person', titles)\n for p in map(Person._make, reader):\n parsed_count += 1\ndiff --git a/apps/lecturers/migrations/0001_initial.py b/apps/lecturers/migrations/0001_initial.py\n--- a/apps/lecturers/migrations/0001_initial.py\n+++ b/apps/lecturers/migrations/0001_initial.py\n@@ -47,8 +47,8 @@ class Migration(migrations.Migration):\n ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n ('category', models.CharField(db_index=True, max_length=1, choices=[('d', 'Didaktisch'), ('m', 'Menschlich'), ('f', 'Fachlich')])),\n ('rating', models.PositiveSmallIntegerField(db_index=True, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(1)])),\n- ('lecturer', models.ForeignKey(related_name='LecturerRating', to='lecturers.Lecturer')),\n- ('user', models.ForeignKey(related_name='LecturerRating', to=settings.AUTH_USER_MODEL)),\n+ ('lecturer', models.ForeignKey(related_name='LecturerRating', to='lecturers.Lecturer', on_delete=django.db.models.deletion.CASCADE)),\n+ ('user', models.ForeignKey(related_name='LecturerRating', to=settings.AUTH_USER_MODEL, on_delete=django.db.models.deletion.CASCADE)),\n ],\n ),\n migrations.CreateModel(\n@@ -59,7 +59,7 @@ class Migration(migrations.Migration):\n ('quote', models.TextField(verbose_name='Zitat')),\n ('comment', models.TextField(default='', verbose_name='Bemerkung', blank=True)),\n ('author', models.ForeignKey(related_name='Quote', on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),\n- ('lecturer', models.ForeignKey(related_name='Quote', verbose_name='Dozent', to='lecturers.Lecturer')),\n+ ('lecturer', models.ForeignKey(related_name='Quote', verbose_name='Dozent', to='lecturers.Lecturer', on_delete=django.db.models.deletion.CASCADE)),\n ],\n options={\n 'ordering': ['-date'],\n@@ -71,8 +71,8 @@ class Migration(migrations.Migration):\n fields=[\n ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),\n ('vote', models.BooleanField(help_text='True = upvote, False = downvote')),\n- ('quote', models.ForeignKey(related_name='QuoteVote', to='lecturers.Quote')),\n- ('user', models.ForeignKey(related_name='QuoteVote', to=settings.AUTH_USER_MODEL)),\n+ ('quote', models.ForeignKey(related_name='QuoteVote', to='lecturers.Quote', on_delete=django.db.models.deletion.CASCADE)),\n+ ('user', models.ForeignKey(related_name='QuoteVote', to=settings.AUTH_USER_MODEL, on_delete=django.db.models.deletion.CASCADE)),\n ],\n ),\n migrations.AlterUniqueTogether(\ndiff --git a/apps/lecturers/models.py b/apps/lecturers/models.py\n--- a/apps/lecturers/models.py\n+++ b/apps/lecturers/models.py\n@@ -35,8 +35,8 @@ class Lecturer(models.Model):\n real_objects = managers.RealLecturerManager()\n \n def name(self):\n- parts = filter(None, [self.title, self.last_name, self.first_name])\n- return ' '.join(parts)\n+ parts = [self.title, self.last_name, self.first_name]\n+ return ' '.join(p for p in parts if p)\n \n def photo(self):\n \"\"\"Try to see if a photo with the name <self.id>.jpg exists. If it\n@@ -64,7 +64,7 @@ def _avg_rating(self, category):\n qs = self.LecturerRating.filter(category=category)\n if qs.exists():\n ratings = qs.values_list('rating', flat=True)\n- return int(round(float(sum(ratings)) / len(ratings)))\n+ return int(sum(ratings) / len(ratings) + 0.5) # always round .5 up\n return 0\n \n def _rating_count(self, category):\n@@ -88,7 +88,7 @@ def rating_count_m(self):\n def rating_count_f(self):\n return self._rating_count('f')\n \n- def __unicode__(self):\n+ def __str__(self):\n return '%s %s' % (self.last_name, self.first_name)\n \n class Meta:\n@@ -103,12 +103,14 @@ class LecturerRating(models.Model):\n ('f', 'Fachlich'))\n RATING_VALIDATORS = [MaxValueValidator(10), MinValueValidator(1)]\n \n- user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='LecturerRating')\n- lecturer = models.ForeignKey(Lecturer, related_name='LecturerRating')\n+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='LecturerRating',\n+ on_delete=models.CASCADE)\n+ lecturer = models.ForeignKey(Lecturer, related_name='LecturerRating',\n+ on_delete=models.CASCADE)\n category = models.CharField(max_length=1, choices=CATEGORY_CHOICES, db_index=True)\n rating = models.PositiveSmallIntegerField(validators=RATING_VALIDATORS, db_index=True)\n \n- def __unicode__(self):\n+ def __str__(self):\n return '%s %s%u' % (self.lecturer, self.category, self.rating)\n \n class Meta:\n@@ -119,7 +121,8 @@ class Quote(models.Model):\n \"\"\"Lecturer quotes.\"\"\"\n author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='Quote', null=True,\n on_delete=models.SET_NULL)\n- lecturer = models.ForeignKey(Lecturer, verbose_name='Dozent', related_name='Quote')\n+ lecturer = models.ForeignKey(Lecturer, verbose_name='Dozent', related_name='Quote',\n+ on_delete=models.CASCADE)\n date = models.DateTimeField(auto_now_add=True)\n quote = models.TextField('Zitat')\n comment = models.TextField('Bemerkung', default='', blank=True)\n@@ -133,7 +136,7 @@ def vote_sum(self):\n down = self.QuoteVote.filter(vote=False).count()\n return up - down\n \n- def __unicode__(self):\n+ def __str__(self):\n return '[%s] %s...' % (self.lecturer, self.quote[:30])\n \n class Meta:\n@@ -142,12 +145,14 @@ class Meta:\n \n \n class QuoteVote(models.Model):\n- \"\"\"Lecturer quotes.\"\"\"\n- user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='QuoteVote')\n- quote = models.ForeignKey(Quote, related_name='QuoteVote')\n+ \"\"\"Lecturer quote votes.\"\"\"\n+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='QuoteVote',\n+ on_delete=models.CASCADE)\n+ quote = models.ForeignKey(Quote, related_name='QuoteVote',\n+ on_delete=models.CASCADE)\n vote = models.BooleanField(help_text='True = upvote, False = downvote')\n \n- def __unicode__(self):\n+ def __str__(self):\n fmt_args = self.user.username, 'up' if self.vote else 'down', self.quote.pk\n return 'User %s votes %s quote %s' % fmt_args\n \n@@ -156,10 +161,10 @@ class Meta:\n \n \n class Course(models.Model):\n- \"\"\"A possible degree course. At the moment only one lecturer is possible\"\"\"\n+ \"\"\"A possible degree course. At the moment only one lecturer is possible.\"\"\"\n id = models.IntegerField('Studiengang ID', primary_key=True)\n abbreviation = models.CharField('Abk\u00fcrzung', max_length=10, unique=True)\n name = models.CharField('Titel', max_length=50)\n \n- def __unicode__(self):\n+ def __str__(self):\n return '%s (%s)' % (self.name, self.abbreviation)\ndiff --git a/apps/lecturers/urls.py b/apps/lecturers/urls.py\n--- a/apps/lecturers/urls.py\n+++ b/apps/lecturers/urls.py\n@@ -4,6 +4,8 @@\n \n admin.autodiscover()\n \n+app_name = 'lecturers'\n+\n # Dynamic pages\n urlpatterns = [\n url(r'^dozenten/$', views.LecturerList.as_view(), name='lecturer_list'),\ndiff --git a/apps/lecturers/views.py b/apps/lecturers/views.py\n--- a/apps/lecturers/views.py\n+++ b/apps/lecturers/views.py\n@@ -3,7 +3,7 @@\n \n from django.contrib import messages\n from django.db.models import Count\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.core.exceptions import ObjectDoesNotExist\n from django.http import HttpResponseForbidden\n from django.views.generic.edit import CreateView, DeleteView\ndiff --git a/apps/tabs/templatetags/tabs.py b/apps/tabs/templatetags/tabs.py\n--- a/apps/tabs/templatetags/tabs.py\n+++ b/apps/tabs/templatetags/tabs.py\n@@ -78,7 +78,7 @@ def render(self, context):\n def activetab(parser, token):\r\n bits = token.contents.split()[1:]\r\n if len(bits) not in (1, 2):\r\n- raise template.TemplateSyntaxError, \"Invalid number of arguments\"\r\n+ raise template.TemplateSyntaxError(\"Invalid number of arguments\")\r\n if len(bits) == 1:\r\n namespace = None\r\n name = bits[0]\r\n@@ -100,7 +100,7 @@ def ifactivetab(parser, token):\n else:\r\n nodelist_false = template.NodeList()\r\n if len(bits) not in (1, 2):\r\n- raise template.TemplateSyntaxError, \"Invalid number of arguments\"\r\n+ raise template.TemplateSyntaxError(\"Invalid number of arguments\")\r\n if len(bits) == 1:\r\n namespace = None\r\n name = bits[0]\r\ndiff --git a/apps/tweets/management/commands/get_twitter_secrets.py b/apps/tweets/management/commands/get_twitter_secrets.py\n--- a/apps/tweets/management/commands/get_twitter_secrets.py\n+++ b/apps/tweets/management/commands/get_twitter_secrets.py\n@@ -39,7 +39,7 @@ def handle_noargs(self, **options):\n sys.exit(1)\n \n self.printO('Please visit {0} in your browser.'.format(redirect_url))\n- verifier = raw_input('Enter PIN: ')\n+ verifier = input('Enter PIN: ')\n \n try:\n auth.get_access_token(verifier)\ndiff --git a/apps/tweets/urls.py b/apps/tweets/urls.py\n--- a/apps/tweets/urls.py\n+++ b/apps/tweets/urls.py\n@@ -8,6 +8,8 @@\n \n admin.autodiscover()\n \n+app_name = 'tweets'\n+\n # Dynamic pages\n urlpatterns = [\n url(r'^$', views.TweetList.as_view(), name='tweet_list'),\ndiff --git a/apps/user_stats/management/commands/plot_user_registrations.py b/apps/user_stats/management/commands/plot_user_registrations.py\n--- a/apps/user_stats/management/commands/plot_user_registrations.py\n+++ b/apps/user_stats/management/commands/plot_user_registrations.py\n@@ -23,7 +23,7 @@ def handle(self, *args, **options):\n User = get_user_model()\n datetimes = User.objects.values_list('date_joined', flat=True) \\\n .order_by('date_joined')\n- dates = map(lambda d: d.date(), datetimes)\n+ dates = [dt.date() for dt in datetimes]\n \n # Get some auxilliary values\n min_date = date2num(dates[0])\ndiff --git a/config/backends.py b/config/backends.py\ndeleted file mode 100644\n--- a/config/backends.py\n+++ /dev/null\n@@ -1,33 +0,0 @@\n-from django.contrib.auth.backends import ModelBackend\n-from django.contrib.auth import get_user_model\n-\n-\n-def strip_mail_part(username):\n- \"\"\"\n- Users might try to login with their email. To support that\n- we can simply strip the mail part from the username\n- \"\"\"\n- if '@' in username:\n- return username.split('@')[0]\n- return username\n-\n-\n-class CaseInsensitiveModelBackend(ModelBackend):\n- \"\"\"\n- By default ModelBackend does case _sensitive_ username authentication, which isn't what is\n- generally expected. This backend supports case insensitive username authentication.\n-\n- Source: http://blog.shopfiber.com/?p=220\n-\n- \"\"\"\n- def authenticate(self, username=None, password=None):\n- username = strip_mail_part(username)\n- User = get_user_model()\n- try:\n- user = User.objects.get(username__iexact=username)\n- if user.check_password(password):\n- return user\n- else:\n- return None\n- except User.DoesNotExist:\n- return None\ndiff --git a/config/settings.py b/config/settings.py\n--- a/config/settings.py\n+++ b/config/settings.py\n@@ -5,7 +5,7 @@\n import sys\n import datetime\n \n-from unipath import Path\n+from pathlib import Path\n \n from django.core.exceptions import ImproperlyConfigured\n \n@@ -21,7 +21,7 @@ def require_env(name):\n return value\n \n \n-PROJECT_ROOT = Path(__file__).ancestor(2)\n+PROJECT_ROOT = Path(__file__).parents[1]\n \n ADMINS = (\n ('Studentenportal Team', 'team@studentenportal.ch'),\n@@ -81,7 +81,7 @@ def require_env(name):\n \n # Absolute filesystem path to the directory that will hold user-uploaded files.\n # Example: \"/home/media/media.lawrence.com/media/\"\n-MEDIA_ROOT = env('DJANGO_MEDIA_ROOT', PROJECT_ROOT.child('media'))\n+MEDIA_ROOT = env('DJANGO_MEDIA_ROOT', PROJECT_ROOT / 'media')\n \n # URL that handles the media served from MEDIA_ROOT. Make sure to use a\n # trailing slash.\n@@ -92,7 +92,7 @@ def require_env(name):\n # Don't put anything in this directory yourself; store your static files\n # in apps' \"static/\" subdirectories and in STATICFILES_DIRS.\n # Example: \"/home/media/media.lawrence.com/static/\"\n-STATIC_ROOT = env('DJANGO_STATIC_ROOT', PROJECT_ROOT.child('static'))\n+STATIC_ROOT = env('DJANGO_STATIC_ROOT', PROJECT_ROOT / 'static')\n \n # URL prefix for static files.\n # Example: \"http://media.lawrence.com/static/\"\n@@ -157,16 +157,18 @@ def require_env(name):\n }\n }]\n \n-MIDDLEWARE_CLASSES = (\n- 'django.middleware.common.CommonMiddleware',\n+MIDDLEWARE = []\n+if DEBUG_TOOLBAR:\n+ MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')\n+MIDDLEWARE += [\n+ 'django.middleware.security.SecurityMiddleware',\n 'django.contrib.sessions.middleware.SessionMiddleware',\n+ 'django.middleware.common.CommonMiddleware',\n 'django.middleware.csrf.CsrfViewMiddleware',\n- 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n 'django.contrib.auth.middleware.AuthenticationMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n-)\n-if DEBUG_TOOLBAR:\n- MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)\n+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n+]\n \n ROOT_URLCONF = 'config.urls'\n \n@@ -272,7 +274,7 @@ def require_env(name):\n \n # Auth\n LOGIN_REDIRECT_URL = '/'\n-AUTHENTICATION_BACKENDS = ('config.backends.CaseInsensitiveModelBackend',)\n+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']\n \n # API\n REST_FRAMEWORK = {\ndiff --git a/config/urls.py b/config/urls.py\n--- a/config/urls.py\n+++ b/config/urls.py\n@@ -21,20 +21,20 @@\n url(r'^statistiken/$', views.Stats.as_view(), name='stats'),\n \n # Own apps\n- url(r'^events/', include(event_urls, namespace='events')),\n- url(r'^dokumente/', include(document_urls, namespace='documents')),\n- url(r'tweets/', include(tweet_urls, namespace='tweets')),\n- url(r'', include(lecturer_urls, namespace='lecturers')),\n+ url(r'^events/', include(event_urls)),\n+ url(r'^dokumente/', include(document_urls)),\n+ url(r'tweets/', include(tweet_urls)),\n+ url(r'', include(lecturer_urls)),\n \n # Auth pages\n url(r'^accounts/', include('registration.backends.default.urls')),\n \n # Admin pages\n url(r'^admin/doc/', include('django.contrib.admindocs.urls')),\n- url(r'^admin/', include(admin.site.urls)),\n+ url(r'^admin/', admin.site.urls),\n \n # API\n- url(r'^api/', include(api_urls, namespace='api')),\n+ url(r'^api/', include(api_urls)),\n \n # Static pages\n url(r'^sitemap\\.xml$', TemplateView.as_view(template_name='front/sitemap.xml'), name='sitemap'),\n@@ -45,3 +45,9 @@\n url(r'static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),\n url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),\n ]\n+\n+if settings.DEBUG_TOOLBAR:\n+ import debug_toolbar\n+ urlpatterns += [\n+ url(r'^__debug__/', include(debug_toolbar.urls)),\n+ ]\n", "test_patch": "diff --git a/requirements/testing.txt b/requirements/testing.txt\n--- a/requirements/testing.txt\n+++ b/requirements/testing.txt\n@@ -1,8 +1,8 @@\n model-bakery==1.0.2\n \n-coverage>=4.5,<5.0\n+coverage>=5.0,<5.1\n six==1.14.0\n-pytest>=4.6,<5\n+pytest>=5.3,<5.4\n pytest-django>=3.8,<3.9\n pytest-pep8>=1.0.6,<1.1\n pytest-cov>=2.8,<2.9\ndiff --git a/tests/api/test_serializers.py b/tests/api/test_serializers.py\n--- a/tests/api/test_serializers.py\n+++ b/tests/api/test_serializers.py\n@@ -2,7 +2,7 @@\n from __future__ import print_function, division, absolute_import, unicode_literals\n \n import pytest\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.contrib.auth import get_user_model\n \n from model_bakery import baker\ndiff --git a/tests/api/test_views.py b/tests/api/test_views.py\n--- a/tests/api/test_views.py\n+++ b/tests/api/test_views.py\n@@ -6,7 +6,7 @@\n \n import pytest\n from django.test.client import Client\n-from django.core.urlresolvers import reverse, NoReverseMatch\n+from django.urls import reverse, NoReverseMatch\n from django.contrib.auth import get_user_model\n \n from model_bakery import baker\n@@ -42,7 +42,7 @@ def test_session_auth(self, auth_client):\n \n def test_basic_auth(self, client, user, db):\n url = reverse('api:quote_list')\n- auth = 'Basic ' + base64.b64encode('testuser:test')\n+ auth = b'Basic ' + base64.b64encode(b'testuser:test')\n resp = client.get(url, HTTP_AUTHORIZATION=auth)\n assert resp.status_code == 200\n \n@@ -57,7 +57,7 @@ def test_status_code(self, user, auth_client):\n 'Status code for %s is %d instead of 200.' % (url, resp.status_code)\n \n def test_list_data(self, auth_client):\n- users = [baker.make(User) for i in xrange(3)]\n+ users = [baker.make(User) for i in range(3)]\n url = reverse('api:user_list')\n resp = auth_client.get(url)\n data = resp.json()\n@@ -120,7 +120,7 @@ def test_status_code(self, lecturer, auth_client):\n 'Status code for %s is %d instead of 200.' % (url, resp.status_code)\n \n def test_detail_data(self, lecturer, auth_client, db):\n- [baker.make(Quote, lecturer=lecturer) for i in xrange(3)]\n+ [baker.make(Quote, lecturer=lecturer) for i in range(3)]\n \n url = reverse('api:lecturer_detail', args=(lecturer.pk,))\n resp = auth_client.get(url)\n@@ -336,7 +336,7 @@ def test_login_required(self, client, url):\n def test_invalid_data(self, data, auth_client, url):\n resp = auth_client.post(url, data)\n assert resp.status_code == 400\n- assert resp.content == 'Validierungsfehler'\n+ assert resp.content == b'Validierungsfehler'\n \n @pytest.fixture\n def rater(self, auth_client, url):\ndiff --git a/tests/documents/test_views.py b/tests/documents/test_views.py\n--- a/tests/documents/test_views.py\n+++ b/tests/documents/test_views.py\n@@ -2,7 +2,7 @@\n from __future__ import print_function, division, absolute_import, unicode_literals\n \n from django.test import TestCase\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.contrib.auth import get_user_model\n \n from bs4 import BeautifulSoup\n@@ -162,20 +162,20 @@ def testTitle(self):\n self.assertContains(self.response, '<h1>Dokumente An1I</h1>')\n \n def testDocumentTitle(self):\n- soup = BeautifulSoup(self.response.content)\n+ soup = BeautifulSoup(self.response.content, 'html.parser')\n div_details = soup.find('h3', text='Analysis 1 Theoriesammlung').find_parent('div').prettify()\n assert '<h3 property=\"dct:title\" xmlns:dct=\"http://purl.org/dc/terms/\">\\n Analysis 1 Theoriesammlung\\n </h3>' in div_details\n assert '<span class=\"label-summary\">\\n Zusammenfassung\\n </span>' in div_details\n \n def testDocumentLicense(self):\n- soup = BeautifulSoup(self.response.content)\n+ soup = BeautifulSoup(self.response.content, 'html.parser')\n div_details = soup.find('h3', text='Analysis 1 Theoriesammlung').find_parent('div').prettify()\n assert '<a href=\"http://creativecommons.org/licenses/by-nc-sa/3.0/deed.de\" rel=\"license\" ' + \\\n u'title=\"Ver\u00f6ffentlicht unter der CC BY-NC-SA 3.0 Lizenz\">' in div_details\n assert ' <span class=\"label-license\">\\n CC BY-NC-SA 3.0\\n </span>' in div_details\n \n def testDocumentFlattr(self):\n- soup = BeautifulSoup(self.response.content)\n+ soup = BeautifulSoup(self.response.content, 'html.parser')\n div_flattr = soup.find('h3', text='Title with Flattr').find_parent('div').prettify()\n div_noflattr = soup.find('h3', text='Title Noflattr').find_parent('div').prettify()\n assert 'Flattr this' in div_flattr\n@@ -220,7 +220,7 @@ def testDownloadCount(self):\n \n # No downloads yet\n response0 = self.client.get(self.url)\n- soup0 = BeautifulSoup(response0.content)\n+ soup0 = BeautifulSoup(response0.content, 'html.parser')\n anchor0 = soup0.find('a', href=dl_url)\n document0 = anchor0.find_parent('article').prettify()\n assert \"0 Downloads\" in document0\n@@ -228,7 +228,7 @@ def testDownloadCount(self):\n # First download\n self.client.get(dl_url)\n response1 = self.client.get(self.url)\n- soup1 = BeautifulSoup(response1.content)\n+ soup1 = BeautifulSoup(response1.content, 'html.parser')\n anchor1 = soup1.find('a', href=dl_url)\n document1 = anchor1.find_parent('article').prettify()\n assert \"1 Download\" in document1\n@@ -237,7 +237,7 @@ def testDownloadCount(self):\n # done from the same IP\n self.client.get(dl_url)\n response2 = self.client.get(self.url)\n- soup2 = BeautifulSoup(response2.content)\n+ soup2 = BeautifulSoup(response2.content, 'html.parser')\n anchor2 = soup2.find('a', href=dl_url)\n document2 = anchor2.find_parent('article').prettify()\n assert \"1 Download\" in document2\ndiff --git a/tests/events/test_views.py b/tests/events/test_views.py\n--- a/tests/events/test_views.py\n+++ b/tests/events/test_views.py\n@@ -5,7 +5,7 @@\n \n from django.test import TestCase\n from django.contrib.auth import get_user_model\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n \n import pytest\n from model_bakery import baker\n@@ -102,7 +102,7 @@ def testContent(self):\n @pytest.mark.django_db(transaction=True)\n def test_ical_event1(client, test_events):\n response = client.get(reverse('events:event_calendar'))\n- event = response.content.split('BEGIN:VEVENT')[1]\n+ event = response.content.decode('utf-8').split('BEGIN:VEVENT')[1]\n assert 'SUMMARY:Weltuntergang' in event\n assert 'DTSTART:20121221T200000' in event\n assert 'DTEND:20121222T100000' in event\n@@ -112,7 +112,7 @@ def test_ical_event1(client, test_events):\n @pytest.mark.django_db(transaction=True)\n def test_ical_event2(client, test_events):\n response = client.get(reverse('events:event_calendar'))\n- event = response.content.split('BEGIN:VEVENT')[2]\n+ event = response.content.decode('utf-8').split('BEGIN:VEVENT')[2]\n assert 'SUMMARY:Afterparty' in event\n assert 'DTSTART:20121222T100000' in event\n assert 'DTEND:20121222T235959' in event\ndiff --git a/tests/front/test_views.py b/tests/front/test_views.py\n--- a/tests/front/test_views.py\n+++ b/tests/front/test_views.py\n@@ -5,8 +5,12 @@\n \n from django.test import TestCase\n from django.core import mail\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.contrib.auth import get_user_model\n+from django.db import transaction\n+\n+from pytest_django.asserts import assertRedirects\n+\n \n from model_bakery import baker\n \n@@ -56,6 +60,29 @@ def testCaseInsensitveLogin(self):\n assert r2.status_code == 302\n \n \n+@pytest.mark.django_db(transaction=True)\n+def test_registration(client):\n+ \"\"\"\n+ Test that a registration is successful and that an activation email is\n+ sent.\n+\n+ Needs to use a transaction because the mail is sent on on_commit.\n+ \"\"\"\n+ registration_url = '/accounts/register/'\n+\n+ response = client.post(registration_url, {\n+ 'email': 'testuser@hsr.ch',\n+ 'password1': 'testpass',\n+ 'password2': 'testpass',\n+ })\n+ assertRedirects(response, '/accounts/register/complete/')\n+ assert User.objects.filter(username='testuser').exists()\n+\n+ transaction.commit()\n+ assert len(mail.outbox) == 1\n+ assert mail.outbox[0].subject == '[studentenportal.ch] Aktivierung'\n+\n+\n class RegistrationViewTest(TestCase):\n registration_url = '/accounts/register/'\n \n@@ -65,21 +92,6 @@ def testRegistrationPage(self):\n self.assertContains(response, 'Diese Registrierung ist Studenten mit einer HSR-Email-Adresse')\n self.assertContains(response, '<form')\n \n- def testRegistration(self):\n- \"\"\"\n- Test that a registration is successful and that an activation email is\n- sent.\n- \"\"\"\n- response = self.client.post(self.registration_url, {\n- 'email': 'testuser@hsr.ch',\n- 'password1': 'testpass',\n- 'password2': 'testpass',\n- })\n- self.assertRedirects(response, '/accounts/register/complete/')\n- assert User.objects.filter(username='testuser').exists()\n- assert len(mail.outbox) == 1\n- assert mail.outbox[0].subject == '[studentenportal.ch] Aktivierung'\n-\n def testRegistrationBadUsername(self):\n \"\"\"\n Test that a registration with a bad username returns an error.\ndiff --git a/tests/lecturers/test_views.py b/tests/lecturers/test_views.py\n--- a/tests/lecturers/test_views.py\n+++ b/tests/lecturers/test_views.py\n@@ -2,7 +2,7 @@\n from __future__ import print_function, division, absolute_import, unicode_literals\n \n from django.test import TestCase\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n from django.contrib.auth import get_user_model\n \n from model_bakery import baker\ndiff --git a/tests/tweets/test_views.py b/tests/tweets/test_views.py\n--- a/tests/tweets/test_views.py\n+++ b/tests/tweets/test_views.py\n@@ -5,7 +5,7 @@\n from collections import namedtuple\n \n from django.test import TestCase\n-from django.core.urlresolvers import reverse\n+from django.urls import reverse\n \n from mock import patch\n \n", "problem_statement": "", "hints_text": "", "created_at": "2020-01-21T17:36:43Z"}2 