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:
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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
@@ -1 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user