diff --git a/combo/apps/assets/templatetags/assets.py b/combo/apps/assets/templatetags/assets.py index 03938d31..da0e81a3 100644 --- a/combo/apps/assets/templatetags/assets.py +++ b/combo/apps/assets/templatetags/assets.py @@ -16,6 +16,7 @@ from django import template from django.db.models.fields.files import ImageFieldFile +from django.utils import six from sorl.thumbnail.shortcuts import get_thumbnail @@ -36,7 +37,7 @@ def asset_url(*args, **kwargs): asset = asset_object break - if isinstance(asset_object, basestring): + if isinstance(asset_object, six.string_types): try: asset = Asset.objects.get(key=asset_object).asset break diff --git a/combo/apps/calendar/utils.py b/combo/apps/calendar/utils.py index 846f0d7b..fb7e001e 100644 --- a/combo/apps/calendar/utils.py +++ b/combo/apps/calendar/utils.py @@ -16,12 +16,11 @@ import datetime import math -import urllib - from django.conf import settings from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.utils.dateparse import parse_datetime +from django.utils.http import urlencode from django.utils.timezone import localtime, make_aware from django.utils.translation import ugettext_lazy as _ @@ -39,7 +38,7 @@ def get_wcs_services(): def get_chrono_service(): - for chrono_key, chrono_site in get_services('chrono').iteritems(): + for chrono_key, chrono_site in get_services('chrono').items(): if not chrono_site.get('secondary', True): chrono_site['slug'] = chrono_key return chrono_site @@ -128,7 +127,7 @@ def get_form_url_with_params(cell, data): } wcs_key, wcs_slug = cell.formdef_reference.split(':') wcs = get_wcs_services().get(wcs_key) - url = '%s%s/?%s' % (wcs['url'], wcs_slug, urllib.urlencode(session_vars)) + url = '%s%s/?%s' % (wcs['url'], wcs_slug, urlencode(session_vars)) return url @@ -189,7 +188,7 @@ class Calendar(object): """return the first available slot that has enough consecutive available slots to be allowed for booking """ - required_contiguous_slots = self.min_duration.seconds / self.offset.seconds + required_contiguous_slots = self.min_duration.seconds // self.offset.seconds for day in self.days: slots = day.slots for idx in range(len(slots) - required_contiguous_slots): diff --git a/combo/apps/calendar/views.py b/combo/apps/calendar/views.py index 136279c6..248590ff 100644 --- a/combo/apps/calendar/views.py +++ b/combo/apps/calendar/views.py @@ -16,6 +16,7 @@ from django.contrib import messages from django.http import HttpResponseRedirect +from django.utils.encoding import force_text from django.views.generic import View, DetailView from django.views.generic.detail import SingleObjectMixin @@ -36,7 +37,7 @@ class BookingView(SingleObjectMixin, View): try: form.is_valid() except ValueError as exc: - messages.error(request, exc.message) + messages.error(request, force_text(exc)) redirect_url = '%s?%s' % ( cell.page.get_online_url(), request.GET.urlencode()) return HttpResponseRedirect(redirect_url) diff --git a/combo/apps/family/utils.py b/combo/apps/family/utils.py index f95f84ae..60b533ac 100644 --- a/combo/apps/family/utils.py +++ b/combo/apps/family/utils.py @@ -21,7 +21,7 @@ from combo.utils import requests def get_passerelle_service(): if hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get('passerelle'): - return settings.KNOWN_SERVICES['passerelle'].values()[0] + return list(settings.KNOWN_SERVICES['passerelle'].values())[0] def is_family_enabled(): return get_passerelle_service() and hasattr(settings, 'FAMILY_SERVICE') diff --git a/combo/apps/lingo/management/commands/notify_new_remote_invoices.py b/combo/apps/lingo/management/commands/notify_new_remote_invoices.py index 9bca307f..b3e17ed7 100644 --- a/combo/apps/lingo/management/commands/notify_new_remote_invoices.py +++ b/combo/apps/lingo/management/commands/notify_new_remote_invoices.py @@ -35,5 +35,5 @@ class Command(BaseCommand): for regie in Regie.objects.exclude(webservice_url=''): try: regie.notify_new_remote_invoices() - except Exception, e: + except Exception as e: logger.exception('error while notifying new remote invoices: %s', e) diff --git a/combo/apps/lingo/manager_views.py b/combo/apps/lingo/manager_views.py index c40c0421..cf761ab4 100644 --- a/combo/apps/lingo/manager_views.py +++ b/combo/apps/lingo/manager_views.py @@ -20,6 +20,7 @@ from dateutil import parser as date_parser from django.core.urlresolvers import reverse_lazy from django.db.models import Q +from django.utils import six from django.utils.timezone import make_aware from django.views.generic import (CreateView, UpdateView, ListView, DeleteView, TemplateView) @@ -93,7 +94,10 @@ def download_transactions_csv(request): str(transaction.amount)] for item in transaction.items.all(): row.extend([item.subject, str(item.amount)]) - writer.writerow([unicode(x).encode('utf-8') for x in row]) + if six.PY3: + writer.writerow([x for x in row]) + else: + writer.writerow([unicode(x).encode('utf-8') for x in row]) return response diff --git a/combo/apps/lingo/models.py b/combo/apps/lingo/models.py index e8478c66..f93bf063 100644 --- a/combo/apps/lingo/models.py +++ b/combo/apps/lingo/models.py @@ -19,7 +19,6 @@ import datetime import json import logging -import urlparse from decimal import Decimal @@ -36,8 +35,10 @@ from django.utils import timezone, dateparse from django.core.mail import EmailMultiAlternatives from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.utils.encoding import python_2_unicode_compatible from django.utils.formats import localize from django.utils.http import urlencode +from django.utils.six.moves.urllib import parse as urlparse from django.contrib.auth.models import User from django.template.loader import render_to_string @@ -84,6 +85,7 @@ def build_remote_item(data, regie): no_online_payment_reason=data.get('no_online_payment_reason')) +@python_2_unicode_compatible class Regie(models.Model): label = models.CharField(verbose_name=_('Label'), max_length=64) slug = models.SlugField(unique=True, verbose_name=_('Identifier'), @@ -127,7 +129,7 @@ class Regie(models.Model): def natural_key(self): return (self.slug,) - def __unicode__(self): + def __str__(self): return self.label def get_text_on_success(self): @@ -271,7 +273,7 @@ class Regie(models.Model): return pending_invoices = self.get_remote_pending_invoices() notification_ids = [] - for uuid, items in pending_invoices.iteritems(): + for uuid, items in pending_invoices.items(): try: user = UserSAMLIdentifier.objects.get(name_id=uuid).user except UserSAMLIdentifier.DoesNotExist: @@ -534,7 +536,7 @@ class LingoBasketCell(CellBase): for items in regies.values(): items['total'] = sum([x.amount for x in items['items']]) - context['regies'] = regies.values() + context['regies'] = sorted(regies.values(), key=lambda x: x['regie'].label) return basket_template.render(context) diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index f469aea7..f06976ff 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -238,7 +238,7 @@ class ValidateTransactionApiView(View): except eopayment.ResponseError as e: logger.error(u'failed in validation operation: %s', e) response = HttpResponse(content_type='application/json') - response.write(json.dumps({'err': 1, 'e': unicode(e)})) + response.write(json.dumps({'err': 1, 'e': force_text(e)})) return response logger.info(u'bank validation result: %r', result) @@ -279,7 +279,7 @@ class CancelTransactionApiView(View): except eopayment.ResponseError as e: logger.error(u'failed in cancel operation: %s', e) response = HttpResponse(content_type='application/json') - response.write(json.dumps({'err': 1, 'e': unicode(e)})) + response.write(json.dumps({'err': 1, 'e': force_text(e)})) return response logger.info(u'bank cancellation result: %r', result) @@ -414,7 +414,7 @@ class PaymentView(View): 'eopayment_order_id': smart_text(payment_response.order_id), 'eopayment_response': repr(payment_response), } - for k, v in payment_response.bank_data.iteritems(): + for k, v in payment_response.bank_data.items(): extra_info['eopayment_bank_data_' + k] = smart_text(v) if not payment_response.signed and not payment_response.result == eopayment.CANCELLED: # we accept unsigned cancellation requests as some platforms do @@ -494,9 +494,9 @@ class CallbackView(PaymentView): try: self.handle_response(request, backend_response, **kwargs) except UnknownPaymentException as e: - raise Http404(unicode(e)) + raise Http404(force_text(e)) except PaymentException as e: - return HttpResponseBadRequest(unicode(e)) + return HttpResponseBadRequest(force_text(e)) return HttpResponse() def get(self, request, *args, **kwargs): diff --git a/combo/apps/maps/models.py b/combo/apps/maps/models.py index b5af1f15..e700e4e0 100644 --- a/combo/apps/maps/models.py +++ b/combo/apps/maps/models.py @@ -18,6 +18,8 @@ import json from django.core import serializers from django.db import models +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse_lazy @@ -88,6 +90,7 @@ class MapLayerManager(models.Manager): return self.get(slug=slug) +@python_2_unicode_compatible class MapLayer(models.Model): objects = MapLayerManager() @@ -123,7 +126,7 @@ class MapLayer(models.Model): self.slug = slug super(MapLayer, self).save(*args, **kwargs) - def __unicode__(self): + def __str__(self): return self.label def natural_key(self): @@ -204,7 +207,7 @@ class MapLayer(models.Model): def match(feature): for geo_property in feature['properties'].values(): - if not isinstance(geo_property, basestring): + if not isinstance(geo_property, six.string_types): continue if query in slugify(geo_property): return True diff --git a/combo/apps/momo/management/commands/update_momo_manifest.py b/combo/apps/momo/management/commands/update_momo_manifest.py index 5395bcd8..68fddfc2 100644 --- a/combo/apps/momo/management/commands/update_momo_manifest.py +++ b/combo/apps/momo/management/commands/update_momo_manifest.py @@ -14,13 +14,12 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from urlparse import urlparse - from django.conf import settings from django.core.management.base import BaseCommand, CommandError from django.db import connection from django.test.client import RequestFactory from django.utils import translation +from django.utils.six.moves.urllib.parse import urlparse from combo.apps.momo.utils import generate_manifest, GenerationError, GenerationInfo @@ -46,5 +45,5 @@ class Command(BaseCommand): raise CommandError(e.message) except GenerationInfo as e: if int(kwargs.get('verbosity')) > 0: - print e.message + print(e.message) translation.deactivate() diff --git a/combo/apps/momo/models.py b/combo/apps/momo/models.py index f22f0f8a..564bb9a8 100644 --- a/combo/apps/momo/models.py +++ b/combo/apps/momo/models.py @@ -84,7 +84,7 @@ class MomoIconCell(CellBase): def get_default_form_class(self): sorted_icons = self._meta.get_field('icon').choices - sorted_icons.sort(lambda x, y: cmp(x[1], y[1])) + sorted_icons.sort(key=lambda x: x[1]) return model_forms.modelform_factory(self.__class__, fields=['icon', 'style', 'description', 'embed_page'], widgets={'icon': Select(choices=sorted_icons)}) diff --git a/combo/apps/momo/views.py b/combo/apps/momo/views.py index 349e22d8..64d5709a 100644 --- a/combo/apps/momo/views.py +++ b/combo/apps/momo/views.py @@ -17,6 +17,7 @@ from django.contrib import messages from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect +from django.utils.encoding import force_text from django.views.generic import TemplateView, UpdateView from .models import MomoOptions @@ -30,9 +31,9 @@ def generate(request, **kwargs): try: generate_manifest(request) except GenerationError as e: - messages.error(request, e.message) + messages.error(request, force_text(e)) except GenerationInfo as e: - messages.info(request, e.message) + messages.info(request, force_text(e)) return HttpResponseRedirect(reverse('momo-manager-homepage')) diff --git a/combo/apps/newsletters/forms.py b/combo/apps/newsletters/forms.py index f510e8ae..4c1ebfb3 100644 --- a/combo/apps/newsletters/forms.py +++ b/combo/apps/newsletters/forms.py @@ -33,7 +33,7 @@ class NewslettersManageForm(forms.Form): self.cleaned_data = {} try: newsletters = self.instance.get_newsletters() - except Exception, e: + except Exception as e: self.add_error(None, _('An error occured while getting newsletters. Please try later.')) logger.error('Error occured while getting newsletters: %r', e) return @@ -46,7 +46,7 @@ class NewslettersManageForm(forms.Form): self.params['mobile'] = self.request.session['mellon_session'].get('mobile', '') try: subscriptions = self.instance.get_subscriptions(self.user, **self.params) - except Exception, e: + except Exception as e: self.add_error(None, _('An error occured while getting subscriptions. Please try later.')) logger.error('Error occured while getting subscriptions: %r', e) return @@ -75,7 +75,7 @@ class NewslettersManageForm(forms.Form): def save(self): self.full_clean() subscriptions = [] - for key, value in self.cleaned_data.iteritems(): + for key, value in self.cleaned_data.items(): subscriptions.append({ 'id': key, 'transports': value diff --git a/combo/apps/newsletters/models.py b/combo/apps/newsletters/models.py index d5d71e06..6a9feee1 100644 --- a/combo/apps/newsletters/models.py +++ b/combo/apps/newsletters/models.py @@ -16,7 +16,6 @@ import logging import json -import urlparse from requests.exceptions import RequestException, HTTPError @@ -70,10 +69,10 @@ class NewslettersCell(CellBase): return slugify(name.strip()) def get_resources_restrictions(self): - return filter(None, map(self.simplify, self.resources_restrictions.strip().split(','))) + return list(filter(None, map(self.simplify, self.resources_restrictions.strip().split(',')))) def get_transports_restrictions(self): - return filter(None, map(self.simplify, self.transports_restrictions.strip().split(','))) + return list(filter(None, map(self.simplify, self.transports_restrictions.strip().split(',')))) def check_resource(self, resource): restrictions = self.get_resources_restrictions() @@ -130,7 +129,7 @@ class NewslettersCell(CellBase): logger.error(u'set subscriptions on %s returned an HTTP error code: %s', response.request.url, response.status_code) raise SubscriptionsSaveError - except RequestException, e: + except RequestException as e: logger.error(u'set subscriptions on %s failed with exception: %s', endpoint, e) raise SubscriptionsSaveError diff --git a/combo/apps/notifications/api_views.py b/combo/apps/notifications/api_views.py index 80a4384f..3dd7c50c 100644 --- a/combo/apps/notifications/api_views.py +++ b/combo/apps/notifications/api_views.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.utils.encoding import force_text + from rest_framework import serializers, permissions, status from rest_framework.generics import GenericAPIView from rest_framework.response import Response @@ -55,7 +57,7 @@ class Add(GenericAPIView): duration=data.get('duration'), ) except ValueError as e: - response = {'err': 1, 'err_desc': {'id': [unicode(e)]}} + response = {'err': 1, 'err_desc': {'id': [force_text(e)]}} return Response(response, status.HTTP_400_BAD_REQUEST) else: response = {'err': 0, 'data': {'id': notification.public_id}} diff --git a/combo/apps/notifications/models.py b/combo/apps/notifications/models.py index a7bbb2c2..dd3941ed 100644 --- a/combo/apps/notifications/models.py +++ b/combo/apps/notifications/models.py @@ -18,6 +18,7 @@ import re from django.conf import settings from django.db import models +from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import now, timedelta from django.db.models import Q @@ -61,6 +62,7 @@ class NotificationQuerySet(QuerySet): end_timestamp=past_end_timestamp, acked=True) +@python_2_unicode_compatible class Notification(models.Model): ID_RE = r'^[\w-]+:[\w-]+$' @@ -82,7 +84,7 @@ class Notification(models.Model): ('user', 'external_id'), ) - def __unicode__(self): + def __str__(self): return self.summary @property @@ -136,7 +138,7 @@ class Notification(models.Model): if id: try: - id = unicode(id) + id = force_text(id) except Exception as e: raise ValueError('id must be convertible to unicode', e) if not re.match(cls.ID_RE, id): diff --git a/combo/apps/wcs/models.py b/combo/apps/wcs/models.py index 06ce1e48..056613b3 100644 --- a/combo/apps/wcs/models.py +++ b/combo/apps/wcs/models.py @@ -500,6 +500,6 @@ class TrackingCodeInputCell(CellBase): def get_cell_extra_context(self, context): extra_context = super(TrackingCodeInputCell, self).get_cell_extra_context(context) if not self.wcs_site: - self.wcs_site = get_wcs_services().keys()[0] + self.wcs_site = list(get_wcs_services().keys())[0] extra_context['url'] = get_wcs_services().get(self.wcs_site).get('url') return extra_context diff --git a/combo/apps/wcs/templates/combo/wcs/current_drafts.html b/combo/apps/wcs/templates/combo/wcs/current_drafts.html index 53b2d2c0..e7b293eb 100644 --- a/combo/apps/wcs/templates/combo/wcs/current_drafts.html +++ b/combo/apps/wcs/templates/combo/wcs/current_drafts.html @@ -1,7 +1,7 @@ {% load i18n combo %} {% block cell-content %}

{% trans 'Current Drafts' %}

-{% for slug, forms in current_drafts.iteritems %} +{% for slug, forms in current_drafts.items %}
{% if forms.data %}
    diff --git a/combo/apps/wcs/templates/combo/wcs/current_forms.html b/combo/apps/wcs/templates/combo/wcs/current_forms.html index cb406819..c0a8679e 100644 --- a/combo/apps/wcs/templates/combo/wcs/current_forms.html +++ b/combo/apps/wcs/templates/combo/wcs/current_forms.html @@ -1,7 +1,7 @@ {% load i18n %} {% block cell-content %}

    {% trans 'Current Forms' %}

    -{% for slug, forms in current_forms.iteritems %} +{% for slug, forms in current_forms.items %}
    {% include "combo/wcs/list_of_forms.html" with forms=forms %}
    diff --git a/combo/apps/wcs/templates/combo/wcs/form_categories.html b/combo/apps/wcs/templates/combo/wcs/form_categories.html index 49cd1b4f..0d6e1e56 100644 --- a/combo/apps/wcs/templates/combo/wcs/form_categories.html +++ b/combo/apps/wcs/templates/combo/wcs/form_categories.html @@ -1,7 +1,7 @@ {% load i18n %} {% block cell-content %}

    {% trans 'Form Categories' %}

    -{% for slug, categories in form_categories.iteritems %} +{% for slug, categories in form_categories.items %}

    {{ categories.title }}

      diff --git a/combo/apps/wcs/templates/combo/wcs/user_all_forms.html b/combo/apps/wcs/templates/combo/wcs/user_all_forms.html index 5a272481..6e0a7156 100644 --- a/combo/apps/wcs/templates/combo/wcs/user_all_forms.html +++ b/combo/apps/wcs/templates/combo/wcs/user_all_forms.html @@ -1,7 +1,7 @@ {% load combo i18n %} {% block cell-content %}

      {% trans 'All Forms' %}

      -{% for slug, forms in user_forms.iteritems %} +{% for slug, forms in user_forms.items %}
      {% include "combo/wcs/list_of_forms.html" with forms=forms %}
      diff --git a/combo/apps/wcs/templates/combo/wcs/user_done_forms.html b/combo/apps/wcs/templates/combo/wcs/user_done_forms.html index ddebc54f..0a9912ce 100644 --- a/combo/apps/wcs/templates/combo/wcs/user_done_forms.html +++ b/combo/apps/wcs/templates/combo/wcs/user_done_forms.html @@ -1,7 +1,7 @@ {% load combo i18n %} {% block cell-content %}

      {% trans 'Done Forms' %}

      -{% for slug, forms in user_forms.iteritems %} +{% for slug, forms in user_forms.items %}
      {% include "combo/wcs/list_of_forms.html" with forms=forms %}
      diff --git a/combo/apps/wcs/utils.py b/combo/apps/wcs/utils.py index 86467d3a..c8ee4941 100644 --- a/combo/apps/wcs/utils.py +++ b/combo/apps/wcs/utils.py @@ -32,7 +32,7 @@ def get_wcs_json(wcs_site, path): def get_wcs_options(url, include_category_slug=False): references = [] - for wcs_key, wcs_site in get_wcs_services().iteritems(): + for wcs_key, wcs_site in get_wcs_services().items(): site_title = wcs_site.get('title') response_json = get_wcs_json(wcs_site, url) if type(response_json) is dict: @@ -51,5 +51,5 @@ def get_wcs_options(url, include_category_slug=False): else: reference = '%s:%s' % (wcs_key, slug) references.append((reference, label)) - references.sort(lambda x, y: cmp(x[1], y[1])) + references.sort(key=lambda x: x[1]) return references diff --git a/combo/apps/wcs/views.py b/combo/apps/wcs/views.py index fcb0ca3d..89377aca 100644 --- a/combo/apps/wcs/views.py +++ b/combo/apps/wcs/views.py @@ -14,10 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import urlparse - from django.contrib import messages from django.http import HttpResponseRedirect, HttpResponseBadRequest +from django.http import HttpResponseRedirect +from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.views.generic import View diff --git a/combo/data/management/commands/import_site.py b/combo/data/management/commands/import_site.py index 43fcb949..c0aaf630 100644 --- a/combo/data/management/commands/import_site.py +++ b/combo/data/management/commands/import_site.py @@ -18,6 +18,7 @@ import json import sys from django.core.management.base import BaseCommand +from django.utils.encoding import force_text from combo.data.utils import import_site, MissingGroups @@ -44,4 +45,4 @@ class Command(BaseCommand): if_empty=options['if_empty'], clean=options['clean']) except MissingGroups as e: - print >> sys.stderr, unicode(e) + sys.stderr.write(force_text(e) + '\n') diff --git a/combo/data/models.py b/combo/data/models.py index 03731b6d..93a99154 100644 --- a/combo/data/models.py +++ b/combo/data/models.py @@ -24,7 +24,6 @@ import os import re import requests import subprocess -import urlparse from django.apps import apps from django.conf import settings @@ -40,9 +39,11 @@ from django.dispatch import receiver from django.forms import models as model_forms from django import forms from django import template -from django.utils.encoding import force_text +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible, force_text, smart_bytes from django.utils.html import strip_tags from django.utils.safestring import mark_safe +from django.utils.six.moves.urllib import parse as urlparse from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from django.forms.widgets import MediaDefiningClass @@ -116,6 +117,7 @@ class PageManager(models.Manager): return queryset.filter(snapshot__isnull=True) +@python_2_unicode_compatible class Page(models.Model): objects = PageManager() snapshots = PageManager(snapshots=True) @@ -149,7 +151,7 @@ class Page(models.Model): class Meta: ordering = ['order'] - def __unicode__(self): + def __str__(self): return self.title def natural_key(self): @@ -349,7 +351,7 @@ class Page(models.Model): del cell['pk'] del cell['fields']['page'] cell['fields']['groups'] = [x[0] for x in cell['fields']['groups']] - for key in cell['fields'].keys(): + for key in list(cell['fields'].keys()): if key.startswith('cached_'): del cell['fields'][key] return serialized_page @@ -357,14 +359,14 @@ class Page(models.Model): @classmethod def load_serialized_page(cls, json_page, snapshot=None): json_page['model'] = 'data.page' - json_page['fields']['groups'] = [[x] for x in json_page['fields']['groups'] if isinstance(x, basestring)] + json_page['fields']['groups'] = [[x] for x in json_page['fields']['groups'] if isinstance(x, six.string_types)] page, created = Page.objects.get_or_create(slug=json_page['fields']['slug'], snapshot=snapshot) json_page['pk'] = page.id page = [x for x in serializers.deserialize('json', json.dumps([json_page]))][0] page.object.snapshot = snapshot page.save() for cell in json_page.get('cells'): - cell['fields']['groups'] = [[x] for x in cell['fields']['groups'] if isinstance(x, basestring)] + cell['fields']['groups'] = [[x] for x in cell['fields']['groups'] if isinstance(x, six.string_types)] if snapshot: cell['fields']['page'] = page.object.id else: @@ -462,8 +464,8 @@ class CellMeta(MediaDefiningClass, ModelBase): pass -class CellBase(models.Model): - __metaclass__ = CellMeta +@python_2_unicode_compatible +class CellBase(six.with_metaclass(CellMeta, models.Model)): page = models.ForeignKey(Page) placeholder = models.CharField(max_length=20) @@ -499,13 +501,13 @@ class CellBase(models.Model): class Meta: abstract = True - def __unicode__(self): - label = unicode(self.get_verbose_name()) + def __str__(self): + label = self.get_verbose_name() additional_label = self.get_additional_label() if label and additional_label: - return '%s (%s)' % (label, re.sub(r'\r?\n', ' ', force_text(additional_label))) + return u'%s (%s)' % (label, re.sub(r'\r?\n', u' ', force_text(additional_label))) else: - return label + return force_text(label) @classmethod def get_verbose_name(cls): @@ -561,7 +563,7 @@ class CellBase(models.Model): if cell_filter and not cell_filter(klass): continue cells.extend(klass.objects.filter(**kwargs)) - cells.sort(lambda x, y: cmp(x.order, y.order)) + cells.sort(key=lambda x: x.order) return cells def get_reference(self): @@ -699,7 +701,7 @@ class CellBase(models.Model): } if not self.is_relevant(context): return '' - from HTMLParser import HTMLParser + from django.utils.six.moves.html_parser import HTMLParser return HTMLParser().unescape(strip_tags(self.render(context))) def get_external_links_data(self): @@ -832,7 +834,7 @@ class LinkCell(CellBase): return context def get_default_form_class(self): - from forms import LinkCellForm + from .forms import LinkCellForm return LinkCellForm def render_for_search(self): @@ -867,7 +869,7 @@ class FeedCell(CellBase): if context.get('placeholder_search_mode'): # don't call webservices when we're just looking for placeholders return extra_context - cache_key = hashlib.md5(self.url).hexdigest() + cache_key = hashlib.md5(smart_bytes(self.url)).hexdigest() feed_content = cache.get(cache_key) if not feed_content: feed_response = requests.get(utils.get_templated_url(self.url)) @@ -886,7 +888,7 @@ class FeedCell(CellBase): return extra_context def render(self, context): - cache_key = hashlib.md5(self.url).hexdigest() + cache_key = hashlib.md5(smart_bytes(self.url)).hexdigest() feed_content = cache.get(cache_key) if not context.get('synchronous') and feed_content is None: raise NothingInCacheException() @@ -1091,13 +1093,13 @@ class JsonCellBase(CellBase): ) except requests.RequestException as e: extra_context[data_key + '_status'] = -1 - extra_context[data_key + '_error'] = unicode(e) + extra_context[data_key + '_error'] = force_text(e) extra_context[data_key + '_exception'] = e logger = logging.getLogger(__name__) if log_errors: - logger.warning(u'error on request %r: %s', url, unicode(e)) + logger.warning(u'error on request %r: %s', url, force_text(e)) else: - logger.debug(u'error on request %r: %s', url, unicode(e)) + logger.debug(u'error on request %r: %s', url, force_text(e)) continue extra_context[data_key + '_status'] = json_response.status_code if json_response.status_code // 100 == 2: @@ -1243,7 +1245,7 @@ class ConfigJsonCell(JsonCellBase): 'group': _('Extra'), 'cell_type_str': cls.get_cell_type_str(), }) - l.sort(lambda x, y: cmp(x.get('name'), y.get('name'))) + l.sort(key=lambda x: x.get('name')) return l def get_label(self): diff --git a/combo/data/utils.py b/combo/data/utils.py index 369902d9..c4655ca6 100644 --- a/combo/data/utils.py +++ b/combo/data/utils.py @@ -16,6 +16,8 @@ from django.contrib.auth.models import Group from django.db import transaction +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from combo.apps.assets.models import Asset @@ -23,11 +25,12 @@ from combo.apps.maps.models import MapLayer from .models import Page +@python_2_unicode_compatible class MissingGroups(Exception): def __init__(self, names): self.names = names - def __unicode__(self): + def __str__(self): return _('Missing groups: %s') % ', '.join(self.names) @@ -51,10 +54,10 @@ def import_site(data, if_empty=False, clean=False): groups = set() for page in data.get('pages') or []: for group in page['fields']['groups']: - groups.add(group if isinstance(group, basestring) else group[0]) + groups.add(group if isinstance(group, six.string_types) else group[0]) for cell in page['cells']: for group in cell['fields']['groups']: - groups.add(group if isinstance(group, basestring) else group[0]) + groups.add(group if isinstance(group, six.string_types) else group[0]) existing_groups = set([x.name for x in Group.objects.filter(name__in=groups)]) missing_groups = groups - existing_groups diff --git a/combo/manager/forms.py b/combo/manager/forms.py index fac2bca9..34ec8066 100644 --- a/combo/manager/forms.py +++ b/combo/manager/forms.py @@ -69,7 +69,7 @@ class PageSelectTemplateForm(forms.ModelForm): super(PageSelectTemplateForm, self).__init__(*args, **kwargs) templates = [(x[0], x[1]['name']) for x in settings.COMBO_PUBLIC_TEMPLATES.items()] templates = [x for x in templates if self.template_exists(x[0])] - templates.sort(lambda x, y: cmp(x[1], y[1])) + templates.sort(key=lambda x: x[1]) if 'template_name' in self.fields: self.fields['template_name'].widget = forms.Select(choices=templates) diff --git a/combo/manager/views.py b/combo/manager/views.py index f7aa3413..8c782c13 100644 --- a/combo/manager/views.py +++ b/combo/manager/views.py @@ -237,7 +237,7 @@ class PageView(DetailView): if 'data' in cell_type_groups.keys(): cell_type_groups[''] = cell_type_groups.get('data') del cell_type_groups['data'] - context['cell_type_groups'] = cell_type_groups.items() + context['cell_type_groups'] = list(cell_type_groups.items()) context['cell_type_groups'].sort(key=lambda x: x[0]) cells = CellBase.get_cells(page_id=self.object.id) diff --git a/combo/public/templatetags/combo.py b/combo/public/templatetags/combo.py index a2a9a4fb..55d9ad20 100644 --- a/combo/public/templatetags/combo.py +++ b/combo/public/templatetags/combo.py @@ -220,3 +220,7 @@ def get_group(group_list, group_name): @register.filter(name='is_empty_placeholder') def is_empty_placeholder(page, placeholder_name): return len([x for x in page.get_cells() if x.placeholder == placeholder_name]) == 0 + +@register.filter(name='list') +def as_list(obj): + return list(obj) diff --git a/combo/public/views.py b/combo/public/views.py index 266db628..33595c5e 100644 --- a/combo/public/views.py +++ b/combo/public/views.py @@ -15,8 +15,6 @@ # along with this program. If not, see . import json -import urllib -import urlparse import django from django.conf import settings @@ -31,6 +29,9 @@ from django.shortcuts import render, resolve_url from django.template import engines from django.template.loader import get_template, TemplateDoesNotExist from django.utils import lorem_ipsum, timezone +from django.utils.encoding import force_text +from django.utils.six.moves.urllib import parse as urlparse +from django.utils.six.moves.urllib import parse as urllib from django.views.decorators.csrf import csrf_exempt from django.utils.translation import ugettext as _ @@ -102,14 +103,14 @@ def ajax_page_cell(request, page_pk, cell_reference): except PostException as e: exception = e if not request.is_ajax(): - messages.error(request, e.message or _('Error sending data.')) + messages.error(request, force_text(e) if force_text(e) != 'None' else _('Error sending data.')) if not request.is_ajax(): return HttpResponseRedirect(cell.page.get_online_url()) response = render_cell(request, cell) if exception: - response['x-error-message'] = exception.message + response['x-error-message'] = force_text(exception) return response def render_cell(request, cell): diff --git a/combo/settings.py b/combo/settings.py index fcf4f355..95bedf4c 100644 --- a/combo/settings.py +++ b/combo/settings.py @@ -315,4 +315,4 @@ NEWSLETTERS_CELL_ENABLED = False local_settings_file = os.environ.get('COMBO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')) if os.path.exists(local_settings_file): - execfile(local_settings_file) + exec(open(local_settings_file).read()) diff --git a/combo/utils/crypto.py b/combo/utils/crypto.py index a6dd9a18..31875e0a 100644 --- a/combo/utils/crypto.py +++ b/combo/utils/crypto.py @@ -20,6 +20,9 @@ from Crypto.Cipher import AES from Crypto.Protocol.KDF import PBKDF2 from Crypto import Random +from django.utils import six +from django.utils.encoding import force_text + class DecryptionError(Exception): pass @@ -33,7 +36,7 @@ def aes_hex_encrypt(key, data): aes_key = PBKDF2(key, iv) aes = AES.new(aes_key, AES.MODE_CFB, iv) crypted = aes.encrypt(data) - return '%s%s' % (binascii.hexlify(iv[:2]), binascii.hexlify(crypted)) + return force_text(b'%s%s' % (binascii.hexlify(iv[:2]), binascii.hexlify(crypted))) def aes_hex_decrypt(key, payload, raise_on_error=True): '''Decrypt data encrypted with aes_base64_encrypt''' @@ -46,10 +49,10 @@ def aes_hex_decrypt(key, payload, raise_on_error=True): try: iv = binascii.unhexlify(iv) * 8 crypted = binascii.unhexlify(crypted) - except TypeError: + except (TypeError, binascii.Error): if raise_on_error: raise DecryptionError('incorrect hexadecimal encoding') return None aes_key = PBKDF2(key, iv) aes = AES.new(aes_key, AES.MODE_CFB, iv) - return aes.decrypt(crypted) + return force_text(aes.decrypt(crypted), 'utf-8') diff --git a/combo/utils/misc.py b/combo/utils/misc.py index 327aba59..d64273e6 100644 --- a/combo/utils/misc.py +++ b/combo/utils/misc.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from HTMLParser import HTMLParser +from django.utils.six.moves.html_parser import HTMLParser from django.template.context import BaseContext from django.utils.html import strip_tags diff --git a/combo/utils/requests_wrapper.py b/combo/utils/requests_wrapper.py index 547fb0f2..6dbde7d4 100644 --- a/combo/utils/requests_wrapper.py +++ b/combo/utils/requests_wrapper.py @@ -16,8 +16,6 @@ import hashlib import logging -from StringIO import StringIO -import urlparse from requests import Response, Session as RequestsSession @@ -25,6 +23,8 @@ from django.conf import settings from django.core.cache import cache from django.utils.encoding import smart_bytes from django.utils.http import urlencode +from django.utils.six.moves.urllib import parse as urlparse +from django.utils.six import BytesIO from .signature import sign_url @@ -110,7 +110,7 @@ class Requests(RequestsSession): if cache_content and not invalidate_cache: response = Response() response.status_code = 200 - response.raw = StringIO(cache_content) + response.raw = BytesIO(smart_bytes(cache_content)) return response elif raise_if_not_cached: raise NothingInCacheException() diff --git a/combo/utils/signature.py b/combo/utils/signature.py index 9d3ab18a..bb56000f 100644 --- a/combo/utils/signature.py +++ b/combo/utils/signature.py @@ -19,10 +19,13 @@ import datetime import hmac import hashlib import random -import urlparse from django.conf import settings +from django.utils.encoding import smart_bytes from django.utils.http import quote, urlencode +from django.utils import six +from django.utils.six.moves.urllib import parse as urlparse + # Simple signature scheme for query strings @@ -50,7 +53,7 @@ def sign_query(query, key, algo='sha256', timestamp=None, nonce=None): def sign_string(s, key, algo='sha256', timedelta=30): digestmod = getattr(hashlib, algo) - hash = hmac.HMAC(str(key), digestmod=digestmod, msg=s) + hash = hmac.HMAC(smart_bytes(key), digestmod=digestmod, msg=smart_bytes(s)) return hash.digest() def check_request_signature(django_request, keys=[]): @@ -60,8 +63,8 @@ def check_request_signature(django_request, keys=[]): orig = django_request.GET.get('orig', '') known_services = getattr(settings, 'KNOWN_SERVICES', None) if known_services and orig: - for services in known_services.itervalues(): - for service in services.itervalues(): + for services in known_services.values(): + for service in services.values(): if 'verif_orig' in service and service['verif_orig'] == orig: keys.append(service['secret']) break @@ -95,8 +98,12 @@ def check_string(s, signature, keys, algo='sha256'): continue res = 0 # constant time compare - for a, b in zip(signature, signature2): - res |= ord(a) ^ ord(b) + if six.PY3: + for a, b in zip(signature, signature2): + res |= a ^ b + else: + for a, b in zip(signature, signature2): + res |= ord(a) ^ ord(b) if res == 0: return True return False diff --git a/combo/utils/urls.py b/combo/utils/urls.py index b8df1fc7..b83e7558 100644 --- a/combo/utils/urls.py +++ b/combo/utils/urls.py @@ -17,6 +17,7 @@ import re from django.conf import settings +from django.utils.encoding import force_text from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist from django.utils.http import quote @@ -58,5 +59,5 @@ def get_templated_url(url, context=None): return '[' if varname not in template_vars: raise TemplateError('unknown variable %s', varname) - return unicode(template_vars[varname]) + return force_text(template_vars[varname]) return re.sub(r'(\[.+?\])', repl, url) diff --git a/setup.py b/setup.py index 0f6bc7af..9e77e900 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ def get_version(): p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE) result = p.communicate()[0] if p.returncode == 0: - version = result.split()[0][1:] + version = str(result.split()[0][1:]) version = version.replace('-', '.') return version return '0' diff --git a/tox.ini b/tox.ini index e2b735fb..2c902174 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ [tox] toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/combo/{env:BRANCH_NAME:} -envlist = django{18,111} +envlist = py2-django18,coverage-py2-django111-pylint,py3-django111 [testenv] usedevelop = True basepython = python2 setenv = - WCSCTL=wcs/wcsctl.py + py2: WCSCTL=wcs/wcsctl.py DJANGO_SETTINGS_MODULE=combo.settings COMBO_SETTINGS_FILE=tests/settings.py deps = @@ -22,10 +22,10 @@ deps = pylint<1.8 pylint-django<0.9 django-webtest<1.9.3 - quixote<3.0 - vobject psycopg2 django-mellon + py2: quixote<3.0 + py2: vobject commands = ./getlasso.sh python manage.py compilemessages