Eine Menge Aufräumarbeiten.

* Eine Testsuite um Mahrjong Ranking Berechnungen zu testen
* Erste Arbeiten um die Workarounds aus dem "utils" Paket los zu werden.
* Vieles am Code umformatiert für PEP8 conformität
This commit is contained in:
2017-06-07 13:25:30 +02:00
parent 690ebec3b0
commit 3e9689c04a
93 changed files with 33531 additions and 2737 deletions

View File

@@ -11,13 +11,14 @@ from .html_cleaner import HtmlCleaner
from .massmailer import MassMailer
CLEANER = HtmlCleaner()
STATUS_REJECTED, STATUS_WAITING, STATUS_PUBLISHED = -1, 0, 1
STATUS_CHOICES = (
(STATUS_REJECTED, _('Rejected')),
(STATUS_WAITING, _('Waiting...')),
(STATUS_PUBLISHED, _('Published')),
)
cleaner = HtmlCleaner()
class OverwriteStorage(FileSystemStorage):

View File

@@ -1,59 +0,0 @@
"""
Created on 24.11.2011
@author: christian
"""
import datetime
from django import forms
class DateInput(forms.widgets.DateInput):
input_type = 'date'
attrs = {'class': 'dateinput'}
def __init__(self, attrs=None, **kwargs):
forms.widgets.DateInput.__init__(self,
attrs=attrs,
format='%Y-%m-%d',
**kwargs)
class NumberInput(forms.widgets.Input):
input_type = 'number'
class TimeInput(forms.widgets.Select):
def __init__(self, attrs=None, ):
timeset = datetime.datetime(2000, 1, 1, 0, 0)
choices = [('', '-------')]
while timeset < datetime.datetime(2000, 1, 2, 0, 0):
choices.append((
timeset.strftime('%H:%M:%S'),
timeset.strftime('%H:%M')
))
timeset += datetime.timedelta(minutes=30)
forms.widgets.Select.__init__(self, attrs=attrs, choices=choices)
def render(self, name, value, attrs=None, choices=()):
return forms.widgets.Select.render(self, name, value, attrs=attrs,
choices=choices)
class SplitDateTimeWidget(forms.widgets.MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
"""
def __init__(self, attrs=None, date_format='%Y-%m-%d'):
widgets = (
DateInput(attrs=attrs, format=date_format),
TimeInput(attrs=attrs)
)
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.date(), value.time()]
return [None, None]

View File

@@ -1,23 +0,0 @@
#!/usr/bin/python
from . import base
registry = base.LookupRegistry()
def url_autodiscover():
import copy
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's lookups module.
try:
before_import_registry = copy.copy(registry._registry)
import_module('%s.lookups' % app)
except:
registry._registry = before_import_registry
if module_has_submodule(mod, 'lookups'):
raise

View File

@@ -1,139 +0,0 @@
import re
from django.core.urlresolvers import reverse
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse
from django.utils.encoding import smart_text, force_text
try:
import json
except ImportError:
from django.utils import simplejson as json
class LookupBase(object):
@classmethod
def name(cls):
app_name = cls.__module__.split('.')[-2].lower()
class_name = cls.__name__.lower()
name = u'%s-%s' % (app_name, class_name)
return name
@classmethod
def url(cls):
return reverse('autocomplete-lookup', args=[cls.name()])
def get_query(self, request, term):
return []
def get_item_label(self, item):
return smart_text(item)
def get_item_id(self, item):
return smart_text(item)
def get_item_value(self, item):
return smart_text(item)
def get_item(self, value):
return value
def create_item(self, value):
raise NotImplemented()
def format_item(self, item):
return {
'id': self.get_item_id(item),
'value': self.get_item_value(item),
'label': self.get_item_label(item)
}
def results(self, request):
term = request.GET.get('term', '')
raw_data = self.get_query(request, term)
data = []
for item in raw_data:
data.append(self.format_item(item))
content = json.dumps(data, cls=DjangoJSONEncoder, ensure_ascii=False)
return HttpResponse(content, content_type='application/json')
class LookupAlreadyRegistered(Exception):
pass
class LookupNotRegistered(Exception):
pass
class LookupInvalid(Exception):
pass
class LookupRegistry(object):
def __init__(self):
self._registry = {}
def validate(self, lookup):
if not issubclass(lookup, LookupBase):
raise LookupInvalid(u'Registered lookups must inherit from the \
LookupBase class')
def register(self, lookup):
self.validate(lookup)
name = force_text(lookup.name())
if name in self._registry:
raise LookupAlreadyRegistered(u'The name %s is already registered'
% name)
self._registry[name] = lookup
def unregister(self, lookup):
self.validate(lookup)
name = force_text(lookup.name())
if name not in self._registry:
raise LookupNotRegistered(u'The name %s is not registered' % name)
del self._registry[name]
def get(self, key):
return self._registry.get(key, None)
class ModelLookup(LookupBase):
model = None
filters = {}
search_field = ''
def get_query(self, request, term):
qs = self.get_queryset()
if term and self.search_field:
qs = qs.filter(**{self.search_field: term})
return qs
def get_queryset(self):
qs = self.model._default_manager.get_query_set()
if self.filters:
qs = qs.filter(**self.filters)
return qs
def get_item_id(self, item):
return item.pk
def get_item(self, value):
item = None
if value:
try:
item = self.get_queryset().filter(pk=value)[0]
except IndexError:
pass
return item
def create_item(self, value):
data = {}
if self.search_field:
field_name = re.sub(r'__\w+$', '', self.search_field)
if field_name:
data = {field_name: value}
return self.model(**data)

View File

@@ -1,250 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
import re
from django.conf import settings
from django.core import validators
from django.core.validators import EMPTY_VALUES
from django.forms import utils, Form, ModelForm, ValidationError
import sys
import django.forms.fields
from django.utils.translation import gettext as _
from . import widgets
class Html5Mixin(object):
def widget_attrs(self, widget):
"""
Overwrites the standard Widget Attributes to add some HTML5 Stuff
:param widget: A Widget Object
"""
attrs = super(Html5Mixin, self).widget_attrs(widget)
if self.required and not isinstance(widget, widgets.CheckboxInput):
attrs['required'] = 'required'
if self.help_text:
attrs['title'] = self.help_text
attrs['placeholder'] = self.help_text
if hasattr(self, 'placeholder'):
attrs['placeholder'] = self.placeholder
if self.accesskey:
attrs['accesskey'] = self.accesskey
return attrs
def __init__(self, *args, **kwargs):
self.accesskey = kwargs.pop('accesskey', None)
super(Html5Mixin, self).__init__(*args, **kwargs)
class AutoCompleteSelectField(Html5Mixin, django.forms.Field):
widget = widgets.AutoCompleteSelectWidget
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. That choice is not one \
of the available choices.'),
}
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
if not kwargs['widget']:
kwargs['widget'] = self.widget(lookup_class,
allow_new=self.allow_new)
super(AutoCompleteSelectField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value in EMPTY_VALUES:
return None
if isinstance(value, list):
# Input comes from an AutoComplete widget. It's two
# components: text and id
if len(value) != 2:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
lookup = self.lookup_class()
if value[1] in EMPTY_VALUES:
if not self.allow_new:
if value[0]:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
else:
return None
value = lookup.create_item(value[0])
else:
value = lookup.get_item(value[1])
if value is None:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
return value
class AutoComboboxSelectField(AutoCompleteSelectField):
widget = widgets.AutoComboboxSelectWidget
class AutoCompleteSelectMultipleField(Html5Mixin, django.forms.Field):
widget = widgets.AutoCompleteSelectMultipleWidget
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. \
That choice is not one of the available choices.'),
}
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
kwargs['widget'] = self.widget(lookup_class)
self.attrs['autofocus'] = 'autofocus'
super(AutoCompleteSelectMultipleField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value in EMPTY_VALUES:
return None
lookup = self.lookup_class()
items = []
for v in value:
if v not in EMPTY_VALUES:
item = lookup.get_item(v)
if item is None:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
items.append(item)
return items
class AutoComboboxSelectMultipleField(AutoCompleteSelectMultipleField):
widget = widgets.AutoComboboxSelectMultipleWidget
class BooleanField(Html5Mixin, django.forms.BooleanField):
widget = widgets.CheckboxInput
class CharField(Html5Mixin, django.forms.CharField):
pass
class DateField(Html5Mixin, django.forms.fields.DateField):
placeholder = _('yyyy-mm-dd')
widget = widgets.DateInput
class DateTimeField(Html5Mixin, django.forms.fields.DateTimeField):
widget = widgets.DateTimeInput
class EmailField(Html5Mixin, django.forms.fields.EmailField):
widget = widgets.EmailInput
class FileField(Html5Mixin, django.forms.fields.FileField):
widget = widgets.NumberInput
pass
class FloatField(Html5Mixin, django.forms.fields.FloatField):
pass
class HiddenInput(Html5Mixin, django.forms.HiddenInput):
pass
class IntegerField(Html5Mixin, django.forms.fields.IntegerField):
widget = widgets.NumberInput
def widget_attrs(self, widget):
attrs = super(IntegerField, self).widget_attrs(widget)
if isinstance(widget, widgets.NumberInput):
if self.min_value is not None:
attrs['min'] = self.min_value
if self.max_value is not None:
attrs['max'] = self.max_value
return attrs
class ModelChoiceField(Html5Mixin, django.forms.ModelChoiceField):
pass
class PasswordInput(Html5Mixin, django.forms.PasswordInput):
pass
class PhoneField(Html5Mixin, django.forms.CharField):
widget = widgets.PhoneInput
def __init__(self, regex=None, max_length=None, min_length=None,
error_message=None, *args, **kwargs):
super(PhoneField, self).__init__(max_length, min_length,
*args, **kwargs)
self._set_regex(u'^[0-9+-/ ]+$')
def _get_regex(self):
return self._regex
def _set_regex(self, regex=u'^[0-9+-/ ]+$'):
regex = re.compile(regex, re.UNICODE)
self._regex = regex
if hasattr(self, '_regex_validator') \
and self._regex_validator in self.validators:
self.validators.remove(self._regex_validator)
self._regex_validator = validators.RegexValidator(regex=regex)
self.validators.append(self._regex_validator)
regex = property(_get_regex, _set_regex)
class ReCaptchaField(django.forms.fields.CharField):
def __init__(self, *args, **kwargs):
self.widget = widgets.ReCaptchaInput
self.required = True
super(ReCaptchaField, self).__init__(*args, **kwargs)
def get_remote_ip(self):
f = sys._getframe()
while f:
if 'request' in f.f_locals:
request = f.f_locals['request']
if request:
remote_ip = request.META.get('REMOTE_ADDR', None)
forwarded_ip = request.META.get('HTTP_X_FORWARDED_FOR', None)
return forwarded_ip or remote_ip
f = f.f_back
def clean(self, values):
""" test the google recaptcha"""
import json, urllib, urllib2
url = "https://www.google.com/recaptcha/api/siteverify"
challenge_value = values[0]
data = urllib.urlencode({
'secret': settings.RECAPTCHA_PRIVATE_KEY,
'response': values[1],
'remoteip': self.get_remote_ip()
})
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
result = json.loads(response.read())
# result["success"] will be True on a success
if not result["success"]:
raise ValidationError(
_(u'Only humans are allowed to submit this form.'))
class RegexField(Html5Mixin, django.forms.RegexField):
pass
class SlugField(Html5Mixin, django.forms.SlugField):
pass
class URLField(Html5Mixin, django.forms.fields.URLField):
widget = widgets.URLInput

View File

@@ -1,148 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
from django.db import models
from django.db.models import ForeignKey # @UnusedImport
from django.utils import six
from . import forms, widgets
class BooleanField(models.BooleanField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.BooleanField}
defaults.update(kwargs)
return super(BooleanField, self).formfield(**defaults)
class CharField(models.CharField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.CharField}
defaults.update(kwargs)
return super(CharField, self).formfield(**defaults)
class DateField(models.DateField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.DateField}
defaults.update(kwargs)
return super(DateField, self).formfield(**defaults)
class DateTimeField(models.DateTimeField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateTimeField}
defaults.update(kwargs)
return super(DateTimeField, self).formfield(**defaults)
class EmailField(models.EmailField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.EmailField}
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
class FileField(models.FileField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.FileField}
defaults.update(kwargs)
return super(FileField, self).formfield(**defaults)
class ForeignKey(models.ForeignKey):
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
if isinstance(self.rel.to, six.string_types):
raise ValueError("Cannot create form field for %r yet, because "
"its related model %r has not been loaded yet" %
(self.name, self.rel.to))
queryset = self.rel.to._default_manager.using(db)
queryset = queryset.complex_filter(self.rel.limit_choices_to)
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': queryset,
'to_field_name': self.rel.field_name,
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)
class ImageField(models.ImageField):
pass
class IntegerField(models.IntegerField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.IntegerField}
defaults.update(kwargs)
return super(IntegerField, self).formfield(**defaults)
class NullBooleanField(models.NullBooleanField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.BooleanField}
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)
class PositiveSmallIntegerField(models.PositiveSmallIntegerField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.IntegerField,
'min_value': 0,
'max_value': 32767
}
defaults.update(kwargs)
return super(PositiveSmallIntegerField, self).formfield(**defaults)
class PositiveIntegerField(models.IntegerField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.IntegerField,
'min_value': 0
}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
class SlugField(models.SlugField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.SlugField}
defaults.update(kwargs)
return super(SlugField, self).formfield(**defaults)
class TextField(models.TextField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.CharField,
'widget': widgets.Textarea
}
defaults.update(kwargs)
return super(TextField, self).formfield(**defaults)
class URLField(models.URLField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.URLField}
defaults.update(kwargs)
return super(URLField, self).formfield(**defaults)

View File

@@ -1,16 +0,0 @@
"""
Created on 05.08.2011
@author: christian
"""
from django.http import Http404
from . import registry
def get_lookup(request, lookup_name):
lookup_cls = registry.get(lookup_name)
if lookup_cls is None:
raise Http404(u'Lookup %s not found' % lookup_name)
lookup = lookup_cls()
return lookup.results(request)

View File

@@ -1,324 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
from django.conf import settings
from django.forms import widgets
from django.forms.utils import flatatt
from django.utils import formats
from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
class AutoCompleteWidget(widgets.TextInput):
def __init__(self, lookup_class, attrs=None, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
self.qs = {}
super(AutoCompleteWidget, self).__init__(*args, **kwargs)
if attrs is not None:
self.attrs = attrs.copy()
else:
self.attrs = {}
def update_query_parameters(self, qs_dict):
self.qs.update(qs_dict)
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(AutoCompleteWidget, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
url = self.lookup_class.url()
if self.qs:
url = '%s?%s' % (url, urlencode(self.qs))
attrs[u'data-selectable-url'] = url
attrs[u'data-selectable-type'] = 'text'
attrs[u'data-selectable-allow-new'] = str(self.allow_new).lower()
return attrs
class AutoComboboxWidget(AutoCompleteWidget):
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(AutoComboboxWidget, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
attrs[u'data-selectable-type'] = 'combobox'
return attrs
class DateInput(widgets.DateInput):
input_type = 'date'
attrs = {'class': 'dateinput'}
def __init__(self, attrs=None, date_format='%Y-%m-%d'):
super(DateInput, self).__init__(attrs)
if date_format:
self.format = date_format
self.manual_format = True
else:
self.format = formats.get_format('DATE_INPUT_FORMATS')[0]
self.manual_format = False
class DateTimeInput(widgets.MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
"""
def __init__(self, attrs=None, date_format='%Y-%m-%d',
time_format='%H:%M'): # @IgnorePep8
widgets = (
DateInput(attrs=attrs, date_format=date_format),
TimeInput(attrs=attrs, time_format=time_format)
)
super(DateTimeInput, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.date(), value.time()]
return [None, None]
class CheckboxInput(widgets.CheckboxInput):
input_type = 'checkbox'
is_checkbox = True
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
title = force_text(self.attrs.get('title', ''))
try:
result = self.check_test(value)
except: # Silently catch exceptions
result = False
if result:
final_attrs['checked'] = 'checked'
if not (
value is True or value is False or value is None or value == ''): # @IgnorePep8
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(value)
return mark_safe(u'<input%s /> %s' % (flatatt(final_attrs), title))
class EmailInput(widgets.TextInput):
input_type = 'email'
class NumberInput(widgets.TextInput):
input_type = 'number'
def __init__(self, attrs=None):
widgets.Input.__init__(self, attrs=attrs)
class PhoneInput(widgets.TextInput):
input_type = 'tel'
class RangeInput(widgets.TextInput):
input_type = 'range'
class ReCaptchaInput(widgets.Widget):
"""
Der HTML Code von Googles ReCaptcha als Form Widget
"""
recaptcha_challenge_name = 'g-recaptcha-response'
recaptcha_response_name = 'g-recaptcha-response'
def render(self, name, value, attrs=None):
html_code = u'''
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="{public_key}" data-size="compact"></div>
'''.format(public_key=settings.RECAPTCHA_PUBLIC_KEY)
return mark_safe(html_code)
def value_from_datadict(self, data, files, name):
return [data.get(self.recaptcha_challenge_name, None),
data.get(self.recaptcha_response_name, None)]
class SelectableMultiWidget(widgets.MultiWidget):
def update_query_parameters(self, qs_dict):
self.widgets[0].update_query_parameters(qs_dict)
class Textarea(widgets.Widget):
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
default_attrs = {'cols': '40', 'rows': '10'}
if attrs:
default_attrs.update(attrs)
super(Textarea, self).__init__(default_attrs)
def render(self, name, value, attrs=None):
value = '' if value is None else value
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
conditional_escape(
force_text(
value))))
class TextInput(widgets.TextInput):
pass
class TimeInput(widgets.TimeInput):
input_type = 'time'
def __init__(self, attrs=None, time_format='%H:%M'):
default_attrs = {'maxlength': 5, 'size': 5}
if attrs:
default_attrs.update(attrs)
super(TimeInput, self).__init__(default_attrs)
if time_format:
self.format = time_format
self.manual_format = True
else:
self.format = formats.get_format('TIME_INPUT_FORMATS')[0]
self.manual_format = False
class URLInput(widgets.Input):
input_type = 'url'
class LookupMultipleHiddenInput(widgets.MultipleHiddenInput):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
super(LookupMultipleHiddenInput, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=()):
lookup = self.lookup_class()
value = [] if value is None else value
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
id_ = final_attrs.get('id', None)
inputs = []
model = getattr(self.lookup_class, 'model', None)
for i, v in enumerate(value):
item = None
if model and isinstance(v, model):
item = v
v = lookup.get_item_id(item)
input_attrs = dict(value=force_text(v), **final_attrs)
if id_:
# An ID attribute was given. Add a numeric index as a suffix
# so that the inputs don't all have the same ID attribute.
input_attrs['id'] = '%s_%s' % (id_, i)
if v:
item = item or lookup.get_item(v)
input_attrs['title'] = lookup.get_item_value(item)
inputs.append(u'<input%s />' % flatatt(input_attrs))
return mark_safe(u'\n'.join(inputs))
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(LookupMultipleHiddenInput, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
attrs[u'data-selectable-type'] = 'hidden-multiple'
return attrs
class AutoCompleteSelectMultipleWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
widgets = [
AutoCompleteWidget(lookup_class, allow_new=False,
attrs={u'data-selectable-multiple': 'true'}),
LookupMultipleHiddenInput(lookup_class)
]
super(AutoCompleteSelectMultipleWidget, self).__init__(widgets, *args,
**kwargs) # @IgnorePep8
def value_from_datadict(self, data, files, name):
return self.widgets[1].value_from_datadict(data, files, name + '_1')
def render(self, name, value, attrs=None):
if value and not hasattr(value, '__iter__'):
value = [value]
value = [u'', value]
return super(AutoCompleteSelectMultipleWidget, self).render(name, value,
attrs) # @IgnorePep8
class AutoComboboxSelectMultipleWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
widgets = [
AutoComboboxWidget(lookup_class, allow_new=False,
attrs={u'data-selectable-multiple': 'true'}),
LookupMultipleHiddenInput(lookup_class)
]
super(AutoComboboxSelectMultipleWidget, self).__init__(widgets, *args,
**kwargs) # @IgnorePep8
def value_from_datadict(self, data, files, name):
return self.widgets[1].value_from_datadict(data, files, name + '_1')
def render(self, name, value, attrs=None):
if value and not hasattr(value, '__iter__'):
value = [value]
value = [u'', value]
return super(AutoComboboxSelectMultipleWidget, self).render(name, value,
attrs) # @IgnorePep8
class AutoCompleteSelectWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
widget_set = [
AutoCompleteWidget(lookup_class, allow_new=self.allow_new),
widgets.HiddenInput(attrs={u'data-selectable-type': 'hidden'})
]
super(AutoCompleteSelectWidget, self).__init__(widget_set, *args,
**kwargs) # @IgnorePep8
def decompress(self, value):
if value:
lookup = self.lookup_class()
model = getattr(self.lookup_class, 'model', None)
if model and isinstance(value, model):
item = value
value = lookup.get_item_id(item)
else:
item = lookup.get_item(value)
item_value = lookup.get_item_value(item)
return [item_value, value]
return [None, None]
class AutoComboboxSelectWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
widget_set = [
AutoComboboxWidget(lookup_class, allow_new=self.allow_new),
widgets.HiddenInput(attrs={u'data-selectable-type': 'hidden'})
]
super(AutoComboboxSelectWidget, self).__init__(widget_set, *args,
**kwargs) # @IgnorePep8
def decompress(self, value):
if value:
lookup = self.lookup_class()
model = getattr(self.lookup_class, 'model', None)
if model and isinstance(value, model):
item = value
value = lookup.get_item_id(item)
else:
item = lookup.get_item(value)
item_value = lookup.get_item_value(item)
return [item_value, value]
return [None, None]

View File

@@ -4,10 +4,14 @@ Created on 19.10.2011
@author: christian
"""
from bs4 import BeautifulSoup
#TODO: Nach BeatutifulSoup 4 convertieren
# TODO: Nach BeatutifulSoup 4 convertieren
class HtmlCleaner(object):
"""
Tries to clean up HTML code, to reomve all possibilities of an XSS Attack
and unwanted inline Javascript and CSS.
"""
ACCEPTABLE_ELEMENTS = ['a', 'abbr', 'acronym', 'address', 'area', 'b',
'big', 'blockquote', 'br', 'button', 'caption',
'center', 'cite',
@@ -23,7 +27,7 @@ class HtmlCleaner(object):
'tfoot', 'th',
'thead', 'tr', 'tt', 'u', 'ul', 'var']
ACCEPTABELE_ATTRIBUTES = [
ACCEPTABLE_ATTRIBUTES = [
'abbr', 'accept', 'accept-charset', 'accesskey',
'action', 'align', 'class', 'alt', 'axis',
'char', 'charoff', 'charset', 'checked', 'cite', 'clear', 'cols',
@@ -35,17 +39,28 @@ class HtmlCleaner(object):
'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title',
'type', 'usemap', 'valign', 'value', 'vspace', 'width']
counter = 1
tag_removed = False
def clean_attributes(self, tag):
"""
reomves all attributes from an element that arenÄt whitelisted.
:param tag: an BeautifulSoup Tag element that should be scrubbed
:return: None
"""
for attr in list(tag.attrs.keys()):
if attr not in self.ACCEPTABELE_ATTRIBUTES:
if attr not in self.ACCEPTABLE_ATTRIBUTES:
del tag[attr]
elif tag[attr].count('script:'):
del tag[attr]
def clean_tag(self, tag):
"""
Removes the entire tag with all its content, when its not on the
whitelist. If the tag is acceptable it will be passed to
clean_attributes
:param tag: BeautifulSoup Tag element that should be scrubbed
:return: None
"""
if tag.name not in self.ACCEPTABLE_ELEMENTS:
tag.extract() # remove the bad ones
self.tag_removed = True
@@ -55,12 +70,12 @@ class HtmlCleaner(object):
def clean_html(self, fragment=''):
"""
Reparses and cleans the html from XSS Attacks until it stops changing.
@param fragment:
:param str fragment: HTML Text that should be cleaned up
:return str: scrubbed HTML Text
"""
while True:
soup = BeautifulSoup(fragment, "html.parser")
self.tag_removed = False
self.counter += 1
for tag in soup.find_all(True):
self.clean_tag(tag)
fragment = str(soup)

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
#!/usr/bin/python

View File

@@ -1 +0,0 @@
#!/usr/bin/python

View File

@@ -1,239 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import fnmatch
from optparse import make_option
import os
import re
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Reads raw CSS from stdin, and writes compressed CSS to stdout.")
option_list = BaseCommand.option_list + (
make_option('-w', '--wrap',
type='int',
default=None,
metavar='N',
help="Wrap output to approximately N chars per line."),
)
def remove_comments(self, css):
"""Remove all CSS comment blocks."""
iemac = False
preserve = False
comment_start = css.find("/*")
while comment_start >= 0:
# Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2)
if comment_end < 0:
if not preserve:
css = css[:comment_start]
break
elif comment_end >= (comment_start + 2):
if css[comment_end - 1] == "\\":
# This is an IE Mac-specific comment; leave this one
# and the following one alone.
comment_start = comment_end + 2
iemac = True
elif iemac:
comment_start = comment_end + 2
iemac = False
elif not preserve:
css = css[:comment_start] + css[comment_end + 2:]
else:
comment_start = comment_end + 2
comment_start = css.find("/*", comment_start)
return css
def remove_unnecessary_whitespace(self, css):
"""Remove unnecessary whitespace characters."""
def pseudoclasscolon(css):
"""
Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later.
"""
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css)
while match:
css = ''.join([
css[:match.start()],
match.group().replace(":", "___PSEUDOCLASSCOLON___"),
css[match.end():]])
match = regex.search(css)
return css
css = pseudoclasscolon(css)
# Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`,
# then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css)
# Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css
def remove_unnecessary_semicolons(self, css):
"""Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css)
def remove_empty_rules(self, css):
"""Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(self, css):
"""Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css)
while match:
colors = match.group(1).split(",")
hexcolor = '#%.2x%.2x%.2x' % map(int, colors)
css = css.replace(match.group(), hexcolor)
match = regex.search(css)
return css
def condense_zero_units(self, css):
"""Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(self, css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0
# 0;`.
css = css.replace("background-position:0;", "background-position:0 0;")
return css
def condense_floating_points(self, css):
"""Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(self, css):
"""Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(
r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css)
while match:
first = match.group(3) + match.group(5) + match.group(7)
second = match.group(4) + match.group(6) + match.group(8)
if first.lower() == second.lower():
css = css.replace(match.group(),
match.group(1) + match.group(2) + '#' + first)
match = regex.search(css, match.end() - 3)
else:
match = regex.search(css, match.end())
return css
def condense_whitespace(self, css):
"""Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css)
def condense_semicolons(self, css):
"""Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css)
def wrap_css_lines(self, css, line_length):
"""Wrap the lines of the given CSS to an approximate length."""
lines = []
line_start = 0
for i, char in enumerate(css):
# It's safe to break after `}` characters.
if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1])
line_start = i + 1
if line_start < len(css):
lines.append(css[line_start:])
return '\n'.join(lines)
def compress(self, css, wrap=None):
css = self.remove_comments(css)
css = self.condense_whitespace(css)
# A pseudo class for the Box Model Hack
# (see http://tantek.com/CSS/Examples/boxmodelhack.html)
css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___")
css = self.remove_unnecessary_whitespace(css)
css = self.remove_unnecessary_semicolons(css)
css = self.condense_zero_units(css)
css = self.condense_multidimensional_zeros(css)
css = self.condense_floating_points(css)
css = self.normalize_rgb_colors_to_hex(css)
css = self.condense_hex_colors(css)
if wrap is not None:
css = self.wrap_css_lines(css, wrap)
css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""')
css = self.condense_semicolons(css)
# A Hack for IE compatiblity
# These crappy browser has issues with AND Statments in @MEDIA Blocks
css = css.replace('and(', 'and (')
return css.strip()
def handle(self, *args, **options):
CSS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'css')
for site in Site.objects.all():
css_input = os.path.join(CSS_ROOT, site.domain)
css_output = '%s/%s.css' % (CSS_ROOT,
site.domain.replace('.', '_'))
print("Compressing CSS for {}".format(site.name))
print("Input Dir: {}".format(css_input))
print("Output File: {}".format(css_output))
try:
os.makedirs(css_input)
except OSError:
pass
css = ''
"""Read each .css file in the css_input directory,
and append their content"""
for file_name in fnmatch.filter(sorted(os.listdir(css_input)),
'*.css'):
print('Adding: {}'.format(file_name))
with open(os.path.join(css_input, file_name), 'r') as css_file:
css += css_file.read()
with open(css_output, 'w') as css_file:
css_file.write(self.compress(css))

View File

@@ -1,47 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import fnmatch
import os
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
import jsmin
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Reads raw CSS from stdin, and writes compressed CSS to stdout.")
def handle(self, *args, **options):
JS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'js')
for site in Site.objects.all():
js_input = os.path.join(JS_ROOT, site.domain)
js_output = '%s/%s.js' % (JS_ROOT, site.domain.replace('.', '_'))
print("Compressing JavaScript for {}".format(site.name))
print("Input Dir: {}".format(js_input))
print("Output File: %s".format(js_output))
try:
os.makedirs(js_input)
except OSError:
pass
output = ''
# Read each .js file in the js_input directory, and append their
# content
js_files = fnmatch.filter(sorted(os.listdir(js_input)), '*.js')
for file_name in js_files:
print(" Adding: {}...".format(file_name))
input_file_name = os.path.join(js_input, file_name)
with open(input_file_name, 'r') as input_file:
output += input_file.read()
with open(js_output, 'w') as output_file:
output_file.write(jsmin.jsmin(output))

View File

@@ -1,47 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import os
import fnmatch
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Compile SCSS rules.")
def handle(self, *args, **options):
CSS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'css')
for site in Site.objects.all():
css_input = os.path.join(CSS_ROOT, site.domain)
css_output = '%s/%s.css' % (CSS_ROOT,
site.domain.replace('.', '_'))
print _("Compressing CSS for %s") % site.name
print "Input Dir: %s" % css_input
print "Output File: %s" % css_output
try:
os.makedirs(css_input)
except OSError:
pass
css = ''
"""Read each .css file in the css_input directory,
and append their content"""
for file_name in fnmatch.filter(sorted(os.listdir(css_input)),
'*.css'):
print ' Adding: %s' % file_name
with open(os.path.join(css_input, file_name), 'r') as css_file:
css += css_file.read()
with open(css_output, 'w') as css_file:
css_file.write(self.compress(css))
# file.write(css)

View File

@@ -2,8 +2,7 @@ import logging
from django.core import mail
from django.contrib.sites.models import Site
from django.conf import settings
from django.template import loader, Context
from django.template import loader
from django.contrib.auth import get_user_model
@@ -48,7 +47,7 @@ class MassMailer(object):
if isinstance(recipient, self.USER_MODEL):
self.recipients.add(recipient)
else:
self.log.warn('%s is not a User Object!', recipient)
self.log.warning('%s is not a User Object!', recipient)
def add_recipients(self, user_list):
for user in user_list:
@@ -56,10 +55,10 @@ class MassMailer(object):
self.log.debug('Adding %s as Recipient' % user)
self.recipients.add(user)
else:
self.log.warn('%s is not a User Object!', user)
self.log.warning('%s is not a User Object!', user)
def process_mails(self):
mail_context = Context(self.context)
mail_context = self.context
mail_context['site'] = Site.objects.get_current()
self.txt_template = loader.get_template(self.txt_template)
@@ -103,7 +102,7 @@ class MassMailer(object):
try:
mail.send()
except:
self.log.warn("Mail failed for: %s", mail.to)
self.log.warning("Mail failed for: %s", mail.to)
else:
self.log.info("Mail sent successful to: %s" % mail.to)
self.close_smtp_connection()

View File

@@ -5,7 +5,8 @@ import html_cleaner
class HtmlTestCase(unittest.TestCase):
known_html = (
('<STRONG>Fett!</STRONG>', '<strong>Fett!</strong>'),
('<a href="link.html" onclick="do_evil()">Bad Link</a>', '<a href="link.html">Bad Link</a>'),
('<a href="link.html" onclick="do_evil()">Bad Link</a>',
'<a href="link.html">Bad Link</a>'),
('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
@@ -32,8 +33,8 @@ class HtmlTestCase(unittest.TestCase):
href="http://www.xxxxxxxxxxxx.com">http://www.xxxxxxxxxxxx.com</A><BR>(xxx)
xxx-xxxx</FONT></DIV></BODY></HTML>
''',
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">'
)
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">'
)
)
def test_html_cleaner(self):
@@ -42,5 +43,6 @@ class HtmlTestCase(unittest.TestCase):
for original, tidy in self.known_html:
self.assertEqual(cleaner.clean_html(original), tidy)
if __name__ == '__main__':
unittest.main()