Commit 7d78e038 authored by Claude Paroz's avatar Claude Paroz

Removed Python 2 support

parent 9d04bdc1
......@@ -10,7 +10,7 @@ Requirements
1 - Django > 1.8.X
2 - Python 2.7 (minimal)
2 - Python 3.4 (minimal)
pillow (python-pillow) for hackergotchi checks.
Markdown (python-markdown) for Team presentation markup rendering.
......@@ -52,13 +52,10 @@ Installation
console:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
2 - Run the following command to create the tables in the database:
./manage.py syncdb
3 - Run the following command to execute the migrations:
2 - Run the following command to execute the database migrations:
./manage.py migrate
4 - In production, you will have to run the following command:
3 - In production, you will have to run the following command:
./manage.py collectstatic
Then configure your Web server to statically serve this directory. For
......@@ -66,24 +63,19 @@ Installation
Alias /static /absolute/path/to/djamnedlies/static
5 - (optional) If you want to populate the database with sample data, run:
4 - (optional) If you want to populate the database with sample data, run:
./manage.py loaddata sample_data
6 - You should now be able to launch the server to check if all is running well:
5 - You should now be able to launch the server to check if all is running well:
./manage.py runserver
7 - Configure Sites in admin interface ('View on site' link, site address in
6 - Configure Sites in admin interface ('View on site' link, site address in
sent mail).
Running tests
=============
To run the tests, you need to install the mock package (in addition to the
runtime dependencies):
pip install mock
And then, run the tests with this command:
To execute the tests, run this command:
python manage.py test [optional path to run partial tests]
......@@ -149,7 +141,6 @@ DATABASES = {
'HOST' = '/var/run/mysqld/mysqld.sock',
'OPTIONS' = {
'read_default_file': '/etc/mysql/my.cnf',
'init_command': 'SET storage_engine=INNODB'
}
}
}
......
# -*- coding: utf-8 -*-
from django.conf import settings
def utils(request):
......
#-*- coding: utf-8 -*-
# From: https://djangosnippets.org/snippets/1979/
import json
......@@ -6,7 +5,6 @@ from django import forms
from django.core import exceptions
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.utils import six
class DictionaryField(models.Field):
......@@ -20,7 +18,7 @@ class DictionaryField(models.Field):
return None
elif value == "":
return {}
elif isinstance(value, six.string_types):
elif isinstance(value, str):
try:
return dict(json.loads(value))
except (ValueError, TypeError):
......@@ -34,7 +32,7 @@ class DictionaryField(models.Field):
def get_prep_value(self, value):
if not value:
return ""
if isinstance(value, six.string_types):
if isinstance(value, str):
return value
else:
return json.dumps(value)
......@@ -44,13 +42,13 @@ class DictionaryField(models.Field):
return self.get_prep_value(value)
def clean(self, value, model_instance):
value = super(DictionaryField, self).clean(value, model_instance)
value = super().clean(value, model_instance)
return self.get_prep_value(value)
def formfield(self, **kwargs):
defaults = {'widget': forms.Textarea}
defaults.update(kwargs)
return super(DictionaryField, self).formfield(**defaults)
return super().formfield(**defaults)
# From https://djangosnippets.org/snippets/1478/
......@@ -66,7 +64,7 @@ class JSONField(models.TextField):
return None
try:
if isinstance(value, six.string_types):
if isinstance(value, str):
return json.loads(value)
except ValueError:
pass
......@@ -82,7 +80,7 @@ class JSONField(models.TextField):
if isinstance(value, (dict, list)):
value = json.dumps(value, cls=DjangoJSONEncoder)
return super(JSONField, self).get_db_prep_save(value, connection=connection)
return super().get_db_prep_save(value, connection=connection)
def value_to_string(self, obj):
return json.dumps(self._get_val_from_obj(obj), cls=DjangoJSONEncoder)
# -*- coding: utf-8 -*-
# This empty file is necessary to run the tests
# -*- coding: utf-8 -*-
#
# Based on https://www.djangosnippets.org/snippets/889/
# Copyright (c) 2008 Stéphane Raimbault <stephane.raimbault@gmail.com>
#
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010 Adorilson Bezerra <adorilson@gmail.com>
# Copyright (c) 2010 Claude Paroz <claude@2xlibre.net>
#
......@@ -18,14 +16,14 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from datetime import datetime, timedelta
import operator
from datetime import datetime, timedelta
from unittest import skipUnless
from unittest.mock import MagicMock, patch
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import translation
from mock import MagicMock, patch
from people.models import Person
from teams.models import Team, Role
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2006-2007 Danilo Segan <danilo@gnome.org>.
# Copyright (c) 2008 Claude Paroz <claude@2xlibre.net>.
#
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008-2011 Claude Paroz <claude@2xlibre.net>.
#
# This file is part of Damned Lies.
......@@ -16,7 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth import login, authenticate, logout
......
# -*- coding: utf-8 -*-
# Django settings for djamnedlies project.
import os
from django.conf import global_settings
......@@ -78,7 +77,7 @@ POTDIR = os.path.join(SCRATCHDIR, "POT")
# The regex is used to determine if the module is in the standard VCS of the project
VCS_HOME_REGEX = "git\.gnome\.org"
VCS_HOME_WARNING = gettext_noop(u"This module is not part of the GNOME Git repository. Please check the module’s web page to see where to send translations.")
VCS_HOME_WARNING = gettext_noop("This module is not part of the GNOME Git repository. Please check the module’s web page to see where to send translations.")
# By default, Django stores files locally, using the MEDIA_ROOT and MEDIA_URL settings
UPLOAD_DIR = 'upload'
......
# -*- coding: utf-8 -*-
from django.conf.urls import include, url
from django.conf import settings
from django.contrib import admin
......
# -*- coding: utf-8 -*-
from django.conf.urls import url
from vertimus.feeds import LatestActionsByLanguage, LatestActionsByTeam
......
# -*- coding: utf-8 -*-
from django.contrib import admin
from languages.models import Language
......
# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
from languages.models import Language
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
......
# -*- coding: utf-8 -*-
from django.core.urlresolvers import NoReverseMatch
from django.db import models
from django.db.models import Q
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from teams.models import Team, FakeTeam
@python_2_unicode_compatible
class Language(models.Model):
name = models.CharField(max_length=50, unique=True)
locale = models.CharField(max_length=15, unique=True)
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011 Claude Paroz <claude@2xlibre.net>
#
# This file is part of Damned Lies.
......
# -*- coding: utf-8 -*-
from django.conf.urls import url
from languages import views
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008 Stéphane Raimbault <stephane.raimbault@gmail.com>
# Copyright (c) 2008-2011 Claude Paroz <claude@2xlibre.net>
#
......
# -*- coding: utf-8 -*-
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
......
# -*- coding: utf-8 -*-
import hashlib, random
from django import forms
......@@ -15,16 +14,16 @@ from people.models import Person
class RegistrationForm(forms.Form):
""" Form for user registration """
username = forms.RegexField(max_length=30, regex=r'^\w+$',
label=ugettext_lazy(u'Choose a username:'),
help_text=ugettext_lazy(u'May contain only letters, numbers, underscores or hyphens'))
email = forms.EmailField(label=ugettext_lazy(u'Email:'))
openid_url = forms.URLField(label=ugettext_lazy(u'OpenID:'),
label=ugettext_lazy('Choose a username:'),
help_text=ugettext_lazy('May contain only letters, numbers, underscores or hyphens'))
email = forms.EmailField(label=ugettext_lazy('Email:'))
openid_url = forms.URLField(label=ugettext_lazy('OpenID:'),
required=False)
password1 = forms.CharField(widget=forms.PasswordInput(render_value=False),
label=ugettext_lazy(u'Password:'), required=False, min_length=7,
help_text=ugettext_lazy(u'At least 7 characters'))
label=ugettext_lazy('Password:'), required=False, min_length=7,
help_text=ugettext_lazy('At least 7 characters'))
password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
label=ugettext_lazy(u'Confirm password:'), required=False)
label=ugettext_lazy('Confirm password:'), required=False)
def clean_username(self):
""" Validate the username (correctness and uniqueness)"""
......@@ -32,7 +31,7 @@ class RegistrationForm(forms.Form):
user = Person.objects.get(username__iexact=self.cleaned_data['username'])
except Person.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))
raise forms.ValidationError(_('This username is already taken. Please choose another.'))
def clean_openid_url(self):
""" Check openid url is not already linked to any existing user """
......@@ -42,7 +41,7 @@ class RegistrationForm(forms.Form):
oid = UserOpenID.objects.get(claimed_id=self.cleaned_data['openid_url'])
except UserOpenID.DoesNotExist:
return self.cleaned_data['openid_url']
raise forms.ValidationError(_(u'This OpenID URL is already taken by a registered user'))
raise forms.ValidationError(_('This OpenID URL is already taken by a registered user'))
else:
return self.cleaned_data['openid_url']
......@@ -52,10 +51,10 @@ class RegistrationForm(forms.Form):
password2 = cleaned_data.get('password2')
openid_url = cleaned_data.get('openid_url')
if not password1 and not openid_url:
raise forms.ValidationError(_(u'You must either provide an OpenID or a password'))
raise forms.ValidationError(_('You must either provide an OpenID or a password'))
if password1 and password1 != password2:
raise forms.ValidationError(_(u'The passwords do not match'))
raise forms.ValidationError(_('The passwords do not match'))
return cleaned_data
def save(self, request):
......@@ -78,10 +77,10 @@ class RegistrationForm(forms.Form):
new_user.save()
# Send activation email
current_site = get_current_site(request)
subject = settings.EMAIL_SUBJECT_PREFIX + _(u'Account activation')
message = _(u"This is a confirmation that your registration on %s succeeded. To activate your account, please click on the link below or copy and paste it in a browser.") % current_site.name
subject = settings.EMAIL_SUBJECT_PREFIX + _('Account activation')
message = _("This is a confirmation that your registration on %s succeeded. To activate your account, please click on the link below or copy and paste it in a browser.") % current_site.name
message += "\n\nhttps://%s%s\n\n" % (current_site.domain, str(reverse("register_activation", kwargs={'key': activation_key})))
message += _(u"Administrators of %s" % current_site.name)
message += _("Administrators of %s" % current_site.name)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
(email,), fail_silently=False)
......@@ -100,13 +99,13 @@ class DetailForm(forms.ModelForm):
return
size = get_image_size(url)
if size[0]>100 or size[1]>100:
raise forms.ValidationError(_(u"Image too high or too wide (%(width)%(height)d, maximum is 100×100 pixels)") % {
raise forms.ValidationError(_("Image too high or too wide (%(width)%(height)d, maximum is 100×100 pixels)") % {
'width': size[0], 'height': size[1]})
return url
class TeamJoinForm(forms.Form):
def __init__(self, *args, **kwargs):
super(TeamJoinForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
# FIXME: exclude team to which user is already member
self.fields['teams'] = forms.ModelChoiceField(queryset=Team.objects.all())
......@@ -120,7 +119,7 @@ def get_image_size(url):
try:
file = urllib.urlopen(url)
except (IOError, UnicodeError, InvalidURL, EOFError):
raise forms.ValidationError(_(u"The URL you provided is not valid"))
raise forms.ValidationError(_("The URL you provided is not valid"))
size = None
p = ImageFile.Parser()
try:
......@@ -133,8 +132,8 @@ def get_image_size(url):
size = p.image.size
break
except Exception as e:
raise forms.ValidationError(u"Sorry, an error occurred while trying to get image size (%s)" % str(e))
raise forms.ValidationError("Sorry, an error occurred while trying to get image size (%s)" % str(e))
file.close()
if not size:
raise forms.ValidationError(_(u"The URL you provided seems not to correspond to a valid image"))
raise forms.ValidationError(_("The URL you provided seems not to correspond to a valid image"))
return size
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
import django.contrib.auth.models
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.contrib.auth.models
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008 Claude Paroz <claude@2xlibre.net>.
# Copyright (c) 2008 Stéphane Raimbault <stephane.raimbault@gmail.com>.
#
......@@ -18,14 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
import re
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
......@@ -40,7 +35,6 @@ def obfuscate_email(email):
return ''
@python_2_unicode_compatible
class Person(User):
""" The User class of D-L. """
......@@ -112,7 +106,7 @@ class Person(User):
if not self.password or self.password == "!":
self.password = None
self.set_unusable_password()
super(User, self).save(*args, **kwargs)
super().save(*args, **kwargs)
def activate(self):
self.activation_key = None
......
from __future__ import unicode_literals
import hashlib
from django import template
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010-2011 Claude Paroz <claude@2xlibre.net>
#
# This file is part of Damned Lies.
......@@ -17,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import datetime
from unittest import skipUnless
......
# -*- coding: utf-8 -*-
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008 Claude Paroz <claude@2xlibre.net>.
#
# This file is part of Damned Lies.
......@@ -16,8 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from operator import itemgetter
from django.conf import settings
......@@ -44,7 +40,7 @@ class PeopleListView(ListView):
model = Person
def get_context_data(self, **kwargs):
context = super(PeopleListView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['pageSection'] = "teams"
return context
......@@ -54,7 +50,7 @@ class PersonDetailView(DetailView):
context_object_name = 'person'
def get_context_data(self, **kwargs):
context = super(PersonDetailView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
states = State.objects.filter(action__person=self.object).distinct()
all_languages = [(lg[0], LANG_INFO.get(lg[0], {'name_local': lg[1]})['name_local']) for lg in settings.LANGUAGES]
all_languages = lc_sorted(all_languages, key=itemgetter(1))
......@@ -74,17 +70,17 @@ class PersonEditView(UpdateView):
def get_object(self):
self.kwargs['slug'] = self.request.user.username
return super(PersonEditView, self).get_object()
return super().get_object()
def get_context_data(self, **kwargs):
context = super(PersonEditView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['pageSection'] = "teams"
context['on_own_page'] = self.object.username == self.request.user.username,
return context
def form_invalid(self, form):
messages.error(self.request, _("Sorry, the form is not valid."))
return super(PersonEditView, self).form_invalid(form)
return super().form_invalid(form)
@login_required
def person_team_join(request):
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008-2011 Claude Paroz <claude@2xlibre.net>.
#
# This file is part of Damned Lies.
......@@ -49,7 +47,7 @@ class DomainInline(admin.StackedInline):
def get_formset(self, request, obj=None, **kwargs):
# Hack! Store parent obj for formfield_for_foreignkey
self.parent_obj = obj
return super(DomainInline, self).get_formset(request, obj, **kwargs)
return super().get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'description':
......@@ -58,12 +56,12 @@ class DomainInline(admin.StackedInline):
kwargs['widget'] = forms.TextInput(attrs={'size':'20'})
elif db_field.name in ('red_filter', 'extra_its_dirs'):
kwargs['widget'] = forms.Textarea(attrs={'rows':'1', 'cols':'40'})
return super(DomainInline, self).formfield_for_dbfield(db_field, **kwargs)
return super().formfield_for_dbfield(db_field, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name in ('branch_from', 'branch_to') and hasattr(self, 'parent_obj') and self.parent_obj:
kwargs['queryset'] = self.parent_obj.branch_set.all()
return super(DomainInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
class ModuleForm(forms.ModelForm):
......@@ -78,7 +76,7 @@ class ModuleForm(forms.ModelForm):
# Delete checkout(s)
for branch in old_module.get_branches(reverse=True): # head branch last
branch.delete_checkout()
instance = super(ModuleForm, self).save(**kwargs)
instance = super().save(**kwargs)
if must_renew_checkout:
for branch in instance.get_branches():
# Force checkout and updating stats
......@@ -102,7 +100,7 @@ class ModuleAdmin(admin.ModelAdmin):
list_filter = ('archived',)
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(ModuleAdmin, self).formfield_for_dbfield(db_field, **kwargs)
field = super().formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'description':
field.widget.attrs['rows'] = '1'
elif db_field.name == 'comment':
......
# -*- coding: utf-8 -*-
import os
import re
from urllib.parse import unquote
from xml.etree.ElementTree import ParseError, parse
from django.utils.encoding import force_text
from django.utils.six.moves.urllib.parse import unquote
from people.models import Person
......
# -*- coding: utf-8 -*-
from django import forms
from django.utils.translation import ugettext as _
......@@ -8,13 +7,13 @@ from stats.models import Branch, Category, CategoryName, Release
class ReleaseField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
kwargs['required'] = False
super(ReleaseField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if 'label' in kwargs:
self.is_branch = True
class ModuleBranchForm(forms.Form):
def __init__(self, module, *args, **kwargs):
super(ModuleBranchForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.module = module
self.branch_fields = []
default_cat_name = None
......@@ -55,7 +54,7 @@ class ModuleBranchForm(forms.Form):
yield (self[rel_field], self[cat_field])
def clean(self):
cleaned_data = super(ModuleBranchForm, self).clean()
cleaned_data = super().clean()
for field_name in cleaned_data.keys():
if (field_name.endswith('_cat') and cleaned_data[field_name] is None
and cleaned_data[field_name[:-4]] is not None):
......
# -*- coding: utf-8 -*-
import csv
import StringIO
from django.core.management.base import BaseCommand, CommandError
......
# -*- coding: utf-8 -*-
import os
import shutil
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008 Claude Paroz <claude@2xlibre.net>.
#
# This file is part of Damned Lies.
......
# -*- coding: utf-8 -*-
import shutil
from django.core.management.base import BaseCommand
from stats.models import Module, Branch
......
# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
from people.models import Person
......
# -*- coding: utf-8 -*-
import sys
from django.core.management.base import BaseCommand
......
# -*- coding: utf-8 -*-
import traceback
from django.core.management.base import BaseCommand, CommandError
......
# -*- coding: utf-8 -*-
import os
import re
import shutil
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
import common.fields
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
CATEGORY_CHOICES = (
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import re
import django.core.validators
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008-2014 Claude Paroz <claude@2xlibre.net>.
# Copyright (c) 2008 Stephane Raimbault <stephane.raimbault@gmail.com>.
#
......@@ -18,8 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import division
from collections import Counter, OrderedDict
import fnmatch
import logging
......@@ -29,15 +25,15 @@ import threading
from datetime import datetime
from functools import total_ordering
from time import sleep
from urllib import request
from urllib.error import URLError
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import RegexValidator
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.encoding import force_text
from django.utils.functional import cached_property