...
 
Commits (134)
......@@ -6,8 +6,8 @@ class CommentInline(admin.TabularInline):
max_num = 1
class ViolationAdmin(admin.ModelAdmin):
list_display = ('state', 'country', 'operator', 'contract', 'resource_name', 'media', 'activationid')
list_filter = ('state', 'operator_ref', 'contract', 'resource_name', 'media', 'country')
list_display = ('state', 'country', 'operator', 'contract', 'resource_name', 'media', 'activationid', 'old')
list_filter = ('state', 'operator_ref', 'contract', 'resource_name', 'media', 'country', 'old')
inlines = [CommentInline, ]
admin.site.register(models.Violation, ViolationAdmin)
......@@ -33,4 +33,4 @@ class OperatorAdmin(admin.ModelAdmin):
list_display = ("__unicode__", "reported_violations")
search_fields = ('name', )
pass
admin.site.register(models.Operator, OperatorAdmin)
\ No newline at end of file
admin.site.register(models.Operator, OperatorAdmin)
......@@ -9,63 +9,63 @@ from operator import itemgetter
from captcha.fields import CaptchaField
class AdvancedEditor(forms.Textarea):
class Media:
js = (settings.MEDIA_URL+'/js/tinymce/tiny_mce.js',)
class Media:
js = (settings.MEDIA_URL+'/js/tinymce/tiny_mce.js',)
def __init__(self, language=None, attrs=None):
self.language = language or settings.LANGUAGE_CODE[:2]
self.attrs = {'class': 'advancededitor'}
if attrs: self.attrs.update(attrs)
super(AdvancedEditor, self).__init__(attrs)
def __init__(self, language=None, attrs=None):
self.language = language or settings.LANGUAGE_CODE[:2]
self.attrs = {'class': 'advancededitor'}
if attrs: self.attrs.update(attrs)
super(AdvancedEditor, self).__init__(attrs)
class AddViolation(forms.Form):
resource_name = forms.CharField(required=True, max_length=4096, label=_('Please specify the affected resource'), help_text=_("What service or site, or person is unavailable or seems artificially slowed down. e.g. VoIP, p2p, filesharing, specific websites, etc."))
country = forms.ChoiceField(required=True, choices=(('',''),)+tuple(sorted(COUNTRIES,key=itemgetter(1))), label=_("Country"), help_text=_('EU member state where the restriction is reported.'))
operator = forms.CharField(required=True, max_length=256, label=_("Operator"), help_text=_('The ISP or operator providing the Internet service.'))
contract = forms.CharField(required=True, max_length=256, label=_("Contract"), help_text=_('The specific contract at the ISP provider. (please be as specific as possible)'))
media = forms.ChoiceField(required=True, choices=(('',''),)+tuple(sorted(MEDIA,key=itemgetter(1))), label=_('Is the Internet connection over mobile or fixed line?'))
comment = forms.CharField(required=True, widget=AdvancedEditor(), label=_('Please describe the symptoms you are experiencing.'))
email = forms.EmailField(required=True, label=_('Email (set this to enable saving)'), help_text=_("We need your email to validate your report. Your email address is obligatory, but we will never use your personal data for anything else than checking the submission. (see next for an optional exception)"))
consent = forms.BooleanField(required=False, label=_("I want to help further"), help_text=_("We need your consent to contact you for clarifications regarding your report. This is optional, but helps us improve the quality of the reports. Thanks!"))
nick = forms.CharField(required=False, label=_("Name or nickname"), help_text=_("We need some name to display that instead of an email address."))
attachments = MultiFileField(required=False, label=_("Attach screenshot, document or any other relevant information."))
resource = forms.ChoiceField(required=False, choices=(('',''),)+tuple(sorted(RESOURCES,key=itemgetter(1))), label=_('What is the affected resource type. (optional)'))
type = forms.ChoiceField(required=False, choices=(('',''),)+tuple(sorted(TYPES,key=itemgetter(1))), label=_('Is the Resource Blocked or otherwise discrimated? (optional)'))
temporary = forms.BooleanField(required=False, label=_('Is the restriction only temporary, e.g. due to network overload? (optional)'))
loophole = forms.BooleanField(required=False, label=_('Is there another offer provided by this Operator which removes this restriction? (optional)'))
contractual = forms.BooleanField(required=False, label=_('Is the restriction described in the subscribers contract? (optional)'))
contract_excerpt = forms.CharField(required=False, widget=AdvancedEditor(), label=_('Please copy the relevant section describing the restriction from the user contract. (optional)'))
captcha = CaptchaField(label=_("In order to protect against spam, please fill in the result of the following calculation. (note the + and the * are somewhat confusing)"))
resource_name = forms.CharField(required=True, max_length=4096, label=_('Please describe the discrimination'), help_text=_("What service or site, or person is unavailable or seems artificially slowed down. e.g. VoIP, p2p, filesharing, specific websites, etc."))
country = forms.ChoiceField(required=True, choices=(('',''),)+tuple(sorted(COUNTRIES,key=itemgetter(1))), label=_("Country"), help_text=_('EU member state where the discrimination is reported.'))
operator = forms.CharField(required=True, max_length=256, label=_("Operator"), help_text=_('The ISP or operator providing the Internet service.'))
contract = forms.CharField(required=True, max_length=256, label=_("Contract"), help_text=_('The specific contract at the ISP provider. (please be as specific as possible)'))
media = forms.ChoiceField(required=True, choices=tuple(sorted(MEDIA,key=itemgetter(1))), label=_('Is the Internet connection over mobile or fixed line?'))
comment = forms.CharField(required=True, widget=AdvancedEditor(), label=_('Please describe the symptoms you are experiencing.'))
email = forms.EmailField(required=True, label=_('Email (set this to enable saving)'), help_text=_("We need your email to validate your report. Your email address is obligatory, but we will never use your personal data for anything else than checking the submission. (see next for an optional exception)"))
consent = forms.BooleanField(required=False, label=_("I want to help further"), help_text=_("We need your consent to contact you for clarifications regarding your report. This is optional, but helps us improve the quality of the reports. Thanks!"))
nick = forms.CharField(required=False, label=_("Name or nickname"), help_text=_("We need some name to display that instead of an email address."))
attachments = MultiFileField(required=False, label=_("Attach screenshot, document or any other relevant information."))
resource = forms.ChoiceField(required=False, choices=RESOURCES, label=_('What is the affected resource type. (optional)'))
temporary = forms.BooleanField(required=False, label=_('Is the discrimination only temporary, e.g. due to network overload? (optional)'))
loophole = forms.BooleanField(required=False, label=_('Is there another offer provided by this Operator which removes this discrimination? (optional)'))
contractual = forms.BooleanField(required=False, label=_('The discrimination is described in the subscribers contract.'))
contract_excerpt = forms.CharField(required=False, widget=AdvancedEditor(), label=_('Please copy the relevant section describing the discrimination from the user contract. (optional)'))
type = forms.ChoiceField(required=True, choices=tuple(TYPES), label=_('How would you describe the discrimination?'))
captcha = CaptchaField(label=_("In order to protect against spam, please fill in the result of the following calculation. (note the + and the * are somewhat confusing)"))
class SearchViolation(SearchForm):
country = forms.ChoiceField(required=True, choices=(('',''),)+tuple(sorted(COUNTRIES,key=itemgetter(1))), label=_("Country"), help_text=_('EU member state where the restriction is reported.'))
operator = forms.CharField(required=True, max_length=256, label=_("Operator"), help_text=_('The ISP or operator providing the Internet service.'))
contract = forms.CharField(required=True, max_length=256, label=_("Contract"), help_text=_('The specific contract at the ISP provider. (please be as specific as possible)'))
media = forms.ChoiceField(required=True, choices=(('',''),)+tuple(sorted(MEDIA,key=itemgetter(1))), label=_('Is the Internet connection over mobile or fixed line?'))
country = forms.ChoiceField(required=False, choices=(('',''),)+tuple(sorted(COUNTRIES,key=itemgetter(1))), label=_("Country"), help_text=_('EU member state where the discrimination is reported.'))
operator = forms.CharField(required=False, max_length=256, label=_("Operator"), help_text=_('The ISP or operator providing the Internet service.'))
contract = forms.CharField(required=False, max_length=256, label=_("Contract"), help_text=_('The specific contract at the ISP provider. (please be as specific as possible)'))
media = forms.ChoiceField(required=False, choices=(('',''),)+tuple(sorted(MEDIA,key=itemgetter(1))), label=_('Is the Internet connection over mobile or fixed line?'))
def search(self):
# By default, teh search field is q. So let's check if it's empty
if not self.cleaned_data['q']:
sqs = SearchQuerySet().all()
else:
sqs = super(SearchViolation, self).search()
def search(self):
# By default, the search field is q. So let's check if it's empty
if not self.cleaned_data['q']:
sqs = SearchQuerySet().all().exclude(old=True)
else:
sqs = super(SearchViolation, self).search().exclude(old=True)
if not self.is_valid():
return self.no_query_found()
if not self.is_valid():
return self.no_query_found()
if self.cleaned_data['operator']:
sqs = sqs.filter(operator=self.cleaned_data['operator'])
if self.cleaned_data['operator']:
sqs = sqs.filter(operator__icontains=self.cleaned_data['operator'])
if self.cleaned_data['contract']:
sqs = sqs.filter(contract=self.cleaned_data['contract'])
if self.cleaned_data['contract']:
sqs = sqs.filter(contract__icontains=self.cleaned_data['contract'])
if self.cleaned_data['media']:
sqs = sqs.filter(media=self.cleaned_data['media'])
if self.cleaned_data['media']:
sqs = sqs.filter(media=self.cleaned_data['media'])
if self.cleaned_data['country']:
sqs = sqs.filter(country=self.cleaned_data['country'])
if self.cleaned_data['country']:
sqs = sqs.filter(country=self.cleaned_data['country'])
return sqs
return sqs
class QuickSearchViolation(forms.Form):
query = forms.CharField(required='True', max_length=256, label=_("Search"), help_text=_('Search for an existing violation'))
......@@ -64,11 +64,11 @@ class Migration(migrations.Migration):
('media', models.CharField(blank=True, max_length=20, choices=[(b'fixed', 'Fixed'), (b'mobile', 'Mobile')])),
('temporary', models.BooleanField()),
('contractual', models.BooleanField()),
('contract_excerpt', models.TextField(blank=True)),
('contract_excerpt', models.TextField(null=True, blank=True)),
('loophole', models.BooleanField()),
('activationid', models.CharField(max_length=128, blank=True)),
('state', models.CharField(default=b'new', max_length=20, blank=True, choices=[(b'moreinfo', 'Need more info'), (b'new', 'New'), (b'verified', 'Verified'), (b'duplicate', 'Duplicate'), (b'ooscope', 'Out of scope'), (b'closed', 'Closed')])),
('editorial', models.TextField(blank=True)),
('editorial', models.TextField(null=True, blank=True)),
('operator_ref', models.ForeignKey(related_name='violations', to='bt.Operator')),
],
),
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bt', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='violation',
name='old',
field=models.BooleanField(default=b'False'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def archive_old(apps, schema_editor):
Violation = apps.get_model("bt", "Violation")
for violation in Violation.objects.all():
violation.old = True
violation.save()
class Migration(migrations.Migration):
dependencies = [
('bt', '0002_violation_old'),
]
operations = [
migrations.RunPython(archive_old),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bt', '0003_auto_20160301_2032'),
]
operations = [
migrations.AlterField(
model_name='violation',
name='activationid',
field=models.CharField(max_length=128, null=True, blank=True),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bt', '0004_auto_20160301_2103'),
]
operations = [
migrations.AlterField(
model_name='violation',
name='country',
field=models.CharField(max_length=2, choices=[(b'BE', 'Belgium'), (b'BG', 'Bulgaria'), (b'CH', 'Switzerland'), (b'CZ', 'Czech Republic'), (b'DK', 'Denmark'), (b'DE', 'Germany'), (b'EE', 'Estonia'), (b'IE', 'Ireland'), (b'EL', 'Greece'), (b'ES', 'Spain'), (b'FR', 'France'), (b'IC', 'Iceland'), (b'IS', 'Iceland'), (b'IT', 'Italy'), (b'CY', 'Cyprus'), (b'LI', 'Liechtenstein'), (b'LV', 'Latvia'), (b'LT', 'Lithuania'), (b'LU', 'Luxembourg'), (b'HU', 'Hungary'), (b'MT', 'Malta'), (b'NL', 'Netherlands'), (b'NO', 'Norway'), (b'AT', 'Austria'), (b'PL', 'Poland'), (b'PT', 'Portugal'), (b'RO', 'Romania'), (b'SI', 'Slovenia'), (b'SK', 'Slovakia'), (b'FI', 'Finland'), (b'SE', 'Sweden'), (b'UK', 'United Kingdom')]),
),
migrations.AlterField(
model_name='violation',
name='resource',
field=models.CharField(blank=True, max_length=20, choices=[(b'port', 'port'), (b'protocol', 'protocol'), (b'service', 'service'), (b'site', 'website'), (b'user', 'user'), (b'ip', 'ip'), (b'video', 'video streaming'), (b'audio', 'audio streaming'), (b'class', 'class of application or contraint'), (b'other', 'other')]),
),
migrations.AlterField(
model_name='violation',
name='type',
field=models.CharField(blank=True, max_length=20, choices=[(b'zerorating', 'Zero Rating'), (b'blocking', 'Blocking'), (b'throttling', 'Throttling'), (b'prioritisation', 'Prioritisation'), (b'specialised', 'Specialised Service'), (b'other', 'Other')]),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
class Migration(migrations.Migration):
dependencies = [
('bt', '0005_auto_20160302_1202'),
]
operations = [
migrations.AddField(
model_name='violation',
name='creation_date',
field=models.DateField(default=datetime.datetime(2016, 3, 2, 12, 14, 23, 907012), auto_now_add=True),
preserve_default=False,
),
]
......@@ -5,6 +5,7 @@ from django_comments.moderation import CommentModerator, moderator
COUNTRIES = (
('BE', _('Belgium')),
('BG', _('Bulgaria')),
('CH', _('Switzerland')),
('CZ', _('Czech Republic')),
('DK', _('Denmark')),
('DE', _('Germany')),
......@@ -13,9 +14,11 @@ COUNTRIES = (
('EL', _('Greece')),
('ES', _('Spain')),
('FR', _('France')),
('IC', _('Iceland')),
('IS', _('Iceland')),
('IT', _('Italy')),
('CY', _('Cyprus')),
('LI', _('Liechtenstein')),
('LV', _('Latvia')),
('LT', _('Lithuania')),
('LU', _('Luxembourg')),
......@@ -33,18 +36,25 @@ COUNTRIES = (
('SE', _('Sweden')),
('UK', _('United Kingdom')),
)
RESOURCES = (
('other', _('other')),
('port', _('port')),
('protocol', _('protocol')),
('service', _('service')),
('site', _('site')),
('site', _('website')),
('user', _('user')),
('ip', _('ip')),
('video', _('video streaming')),
('audio', _('audio streaming')),
('class', _('class of application or contraint')),
)
TYPES = (
('zerorating', _('Zero Rating')),
('specialised', _('Specialised Service')),
('blocking', _('Blocking')),
('throttling', _('Throttling')),
('prioritisation', _('Prioritisation')),
('other', _('Other'))
)
MEDIA = (
('fixed', _('Fixed')),
......@@ -73,7 +83,6 @@ class Operator(models.Model):
def __unicode__(self):
return self.name
class Violation(models.Model):
country = models.CharField(max_length=2, choices=COUNTRIES)
operator_ref = models.ForeignKey(Operator, related_name="violations")
......@@ -84,11 +93,13 @@ class Violation(models.Model):
media = models.CharField( max_length=20, choices=MEDIA, blank=True)
temporary = models.BooleanField( )
contractual = models.BooleanField()
contract_excerpt = models.TextField(blank=True)
contract_excerpt = models.TextField(null=True, blank=True)
loophole = models.BooleanField()
activationid= models.CharField(max_length=128, blank=True)
activationid= models.CharField(max_length=128, null=True, blank=True)
state = models.CharField(max_length=20, choices=STATUS, default='new', blank=True)
editorial = models.TextField(blank=True)
editorial = models.TextField(null=True, blank=True)
old = models.BooleanField(default="False")
creation_date = models.DateField(auto_now_add=True)
def confirmations(self):
return self.confirmation_set.filter(key='').count()
......@@ -108,7 +119,6 @@ class Violation(models.Model):
def __unicode__(self):
return "#%s %s/%s" % (self.pk, self.country, self.operator)
class Comment(models.Model):
submitter_email = models.EmailField()
submitter_name = models.CharField(max_length=20)
......
......@@ -6,30 +6,23 @@ Released into the Public Domain
"""
from django.utils.encoding import force_unicode
from django.utils.datastructures import MultiValueDict
from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.translation import ugettext
from django.forms.fields import Field, EMPTY_VALUES
from django.core.files.uploadedfile import UploadedFile
from django.forms.widgets import FileInput
from django.forms.widgets import Input, FILE_INPUT_CONTRADICTION
from django.forms.util import ErrorList, ValidationError, flatatt
from django.utils.safestring import mark_safe
class MultiFileInput(FileInput):
FILE_INPUT_EMPTY_VALUE = object()
class MultiFileInput(Input):
"""
A widget to be used by the MultiFileField to allow the user to upload
multiple files at one time.
"""
def __init__(self, attrs=None, *args, **kwargs):
"""
Create a MultiFileInput.
The 'count' attribute can be specified to default the number of
file boxes initially presented.
"""
super(MultiFileInput, self).__init__(attrs, *args, **kwargs)
self.attrs = {'count':1}
if attrs:
self.attrs.update(attrs)
input_type = 'file'
needs_multipart_form = True
def render(self, name, value, attrs=None):
"""
......@@ -37,56 +30,23 @@ class MultiFileInput(FileInput):
Should not be overridden. Instead, subclasses should override the
js, link, and/or fields methods which provide content to this method.
"""
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name+'[]')
count = final_attrs['count']
if count<1: count=1
del final_attrs['count']
js = self.js(name, value, count, final_attrs)
link = self.link(name, value, count, final_attrs)
fields = self.fields(name, value, count, final_attrs)
return mark_safe(js+link+fields)
def fields(self, name, value, count, attrs=None):
"""
Renders the necessary number of file input boxes.
"""
return u''.join([u'<input class="attachments"%s />\n' % flatatt(dict(attrs, id=attrs['id']+str(i))) for i in range(count)])
if attrs is None:
attrs = {}
def link(self, name, value, count, attrs=None):
"""
Renders a link to add more file input boxes.
"""
return u"<div><a id='add_attach' onclick=\"javascript:new_%(name)s()\">Add more attachments</a></div>" % {'name':name}
name += '[]'
attrs['multiple'] = 'multiple'
def js(self, name, value, count, attrs=None):
"""
Renders a bit of Javascript to add more file input boxes.
"""
return u"""
<script>
<!--
%(id)s_counter=%(count)d;
function new_%(name)s() {
b=document.getElementById('%(id)s0');
c=b.cloneNode(false);
c.id='%(id)s'+(%(id)s_counter++);
b.parentNode.insertBefore(c,b);
}
-->
</script>
""" % {'id':attrs['id'], 'name':name, 'count':count}
return super(MultiFileInput, self).render(name, None, attrs=attrs)
def value_from_datadict(self, data, files, name):
"""
File widgets take data from FILES, not POST.
File widget takes data from FILES, not POST
we need to add [] for w3c recoomendation
"""
name = name+'[]'
if isinstance(files, MultiValueDict):
name += '[]'
if isinstance(files, (MultiValueDict, MergeDict)):
return files.getlist(name)
else:
return None
return files.get(name, None)
def id_for_label(self, id_):
"""
......@@ -102,56 +62,37 @@ class MultiFileField(Field):
A field allowing users to upload multiple files at once.
"""
widget = MultiFileInput
count = 1
max_length = None
allow_empty_file = None
def __init__(self, count=1, strict=False, *args, **kwargs):
"""
strict is whether the number of files uploaded must equal count
"""
self.count = count
self.strict = strict
super(MultiFileField, self).__init__(*args, **kwargs)
def widget_attrs(self, widget):
"""
Adds the count to the MultiFileInput widget.
"""
if isinstance(widget, MultiFileInput):
return {'count':self.count}
return {}
def clean(self, data):
def to_python(self, data):
"""
Cleans the data and makes sure that all the files had some content.
Also checks whether a file was required.
"""
super(MultiFileField, self).clean(data)
if not self.required and data in EMPTY_VALUES:
if data in EMPTY_VALUES:
return None
try:
f = map(lambda a: UploadedFile(a['filename'], a['content']), data)
except TypeError:
raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form."))
except KeyError:
raise ValidationError(ugettext(u"No file was submitted."))
for a_file in f:
if not a_file.content:
raise ValidationError(ugettext(u"The submitted file is empty."))
if self.strict and len(f) != self.count:
raise ValidationError(ugettext(u"An incorrect number of files were uploaded."))
return f
class FixedMultiFileInput(MultiFileInput):
"""
A MultiFileInput widget that doesn't print the javascript code to allow
the user to add more file input boxes.
"""
def link(self, name, value, count, attrs=None):
return u''
def js(self, name, value, count, attrs=None):
return u''
if data is FILE_INPUT_EMPTY_VALUE:
raise validationError(self.Error_messages['empty_multiply'])
# UploadedFile objects should have name and size attributes
for d in data:
try:
file_name = d.name
file_size = d.size
except AttributeError:
raise ValidationError(self.Error_messages['invalid'])
if self.max_length is not None and file_size > self.max_length:
error_values = {'max': self.max_length, 'length': file_size}
raise ValidationError(self.error_messages['max_length'] % error_values)
if not file_name:
raise ValidationError(self.error_messages['invalid'])
if not self.allow_empty_file and not file_size:
raise ValidationError(self.Error_messages['empty'])
return data
def bound_data(self, data, initial):
if data in (None, FILE_INPUT_EMPTY_VALUE, FILE_INPUT_CONTRADICTION):
return initial
return data
......@@ -5,9 +5,12 @@ class ViolationIndexes(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
operator = indexes.CharField(model_attr="operator_ref")
country = indexes.CharField(model_attr="country")
contract = indexes.CharField(model_attr="contract_excerpt")
type = indexes.BooleanField(model_attr="contractual")
contract = indexes.CharField(model_attr="contract")
type = indexes.CharField(model_attr="contractual")
media = indexes.CharField(model_attr="media")
operator_name = indexes.CharField()
state = indexes.NgramField(model_attr="state")
old = indexes.BooleanField(model_attr="old")
def get_model(self):
return Violation
......
from django.core.serializers import serialize
from django.db.models.query import QuerySet
from django.template import Library
from haystack.models import SearchResult
import json
register = Library()
@register.filter(name='jsonify')
def jsonify(object):
if isinstance(object, QuerySet):
return serialize('json', object)
elif object == []:
return json.dumps(object)
elif isinstance(object[0], SearchResult):
return serialize('json', [x.object for x in object])
else:
return json.dumps(object)
This diff is collapsed.
......@@ -4,13 +4,15 @@ BeautifulSoup==3.2.1
defusedxml==0.4.1
Django==1.8.6
django-ajax-selects==1.4.1
django-bootstrap3==7.0.0
django-contrib-comments==1.6.1
django-debug-toolbar==1.4
django-haystack==2.4.1
django-registration==2.0.3
django-simple-captcha==0.4.6
django-tastypie==0.12.2
django-tinymce==2.0.6
flup==1.0.3.dev20110405
flup
httplib2==0.9.2
lxml==3.5.0
oauth2==1.9.0.post1
......@@ -24,5 +26,6 @@ pytz==2015.7
simplejson==3.8.1
six==1.10.0
South==1.0.2
sqlparse==0.1.18
wheel==0.24.0
Whoosh==2.7.0
# Django settings for nnmon project.
from django.utils.translation import ugettext_lazy as _
import os
BASE_PATH = os.path.dirname(__file__)
DEBUG = True
......@@ -41,6 +42,13 @@ TIME_ZONE = 'Europe/Brussels'
# http:/www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en'
# Languages supported by the website
LANGUAGES = [
('fr', _('French')),
('en', _('English')),
('es', _('Spanish')),
]
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
......@@ -131,22 +139,27 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'django_comments',
# Reduce the spam in comments
'spamlesscomment',
'tinymce',
#'registration',
'bt',
#'babeldjango',
'captcha',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'tastypie',
# Register users
'registration',
# Haystack for searching through results
'haystack',
# Debugging bar
'debug_toolbar',
# Let's have fun with bootstrap
'bootstrap3',
)
COMMENTS_APP = 'spamlesscomment'
TEMPLATE_CONTEXT_PROCESSORS = ("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
......@@ -155,6 +168,14 @@ TEMPLATE_CONTEXT_PROCESSORS = ("django.contrib.auth.context_processors.auth",
'django.core.context_processors.request',
"django.contrib.messages.context_processors.messages")
LOCALE_PATHS = (
os.path.join(BASE_PATH, 'locale/'),
)
LANGUAGES = (
('en', 'English'),
('fr', 'Francais'),
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
......
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
body { border: 0; padding: 0; font-family: Verdana,Arial,Helvetica,sans-serif; }
body { border: 0; padding: 0; font-family: Verdana,Arial,Helvetica,sans-serif; text-align: justify }
img { border: 0; vertical-align: middle; }
h1, h2, h3, h4, div, ul { padding: 0; margin: 0; }
li { list-style: none; }
h1, h2, h3, h4, div { padding: 0; margin: 0; }
.fieldWrapper { width: 100%; position: relative; }
.fieldWrapper label { display: inline-block; }
......@@ -49,7 +48,8 @@ li { list-style: none; }
#addForm .fieldWrapper label { display: inline-block; min-width: 6em; }
#addForm.index .help_text { font-size: 0.7em; width: 30%; position: relative; float: right; left: 0; }
.index #add_attach { cursor: pointer; }
#id_captcha_1 { width: 2em; }
img.captcha {float: left; }
#id_captcha_1 { width: auto; }
.innerfield { display: inline-block; margin-top: 0.5em;}
.align-right { text-align: right;}
dd.text { width: 50%; display: block !important; }
......@@ -76,10 +76,10 @@ p label { width: 7em; display: inline-block; }
#footer {
text-align: center;
max-width: 960px;
clear: both;
margin: auto;
background-color: #f9f9f9;
padding-bottom:30px;
}
.errorlist { font-size: .8em; color: red; }
......@@ -237,26 +237,28 @@ blockquote {
}
#header {
background: #f9f9f9;
padding:3%
}
#header a {
color: #222;
color: white;
text-decoration: none;
}
h1 {
margin: 25px 0 0 0; padding: 30px 0;
margin: 25px 0 0 0;
padding: 3%;
font-family: 'DroidSansBold', Helvetica, Arial, "Liberation Sans", FreeSans, sans-serif;
font-size: 35px;
line-height: 1;
font-weight: normal;
font-weight: bold;
text-shadow:1px 2px 1px rgba(0, 0, 0, 0.4);
}
h1 span {
display: block;
font-family: 'DroidSansRegular', Helvetica, Arial, "Liberation Sans", FreeSans, sans-serif;
font-size: .8em;
font-weight: normal;
color: orange;
font-weight: bold;
}
/* User */
......@@ -279,7 +281,6 @@ h1 span {
}
#user p {
width: 960px;
margin: 0 auto;
text-align: right;
}
......@@ -288,61 +289,51 @@ h1 span {
color: orange;
}
/* Navigation menu */
#nav {
height: 36px;
margin: 0; padding: 0;
list-style-type: none;
border-bottom: 1px solid #ddd;
}
#nav li {
display: inline-block;
}
#nav #current {
color: #231f20;
background: #fff;
border: 1px solid #ddd;
border-bottom: 1px solid #fff;
}
#nav #current:hover, #nav #current:active, #nav #current:focus {
color: orange;
}
#nav a {
display: block;
height: 19px;
padding: 8px 1em;
text-decoration: none;
color: #ccc;
border: 1px solid #f0f0f0;
border-bottom-color: #ddd;
-moz-border-radius: 2px 2px 0 0;
-webkit-border-radius: 2px 2px 0 0;
-o-border-radius: 2px 2px 0 0;
border-radius: 2px 2px 0 0;
}
#nav a:hover, #nav a:active, #nav a:focus {
color: #231f20;
background: #fff;
border: 1px solid #ddd;
border-bottom-color: #fff;
outline: none;
}
/* Content layout
---------------------------*/
#global {
position: relative;
width: 960px;
margin: 25px auto 50px auto;
}
/* Content */
#content {
width: 610px;
width: 60%;
}
#add-case{
width: 40%;
}
#header{
background: rgba(11,145,194,1);
background: -moz-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background: -webkit-gradient(left top, right top, color-stop(0%, rgba(11,145,194,1)), color-stop(100%, rgba(4,220,227,1)));
background: -webkit-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background: -o-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background: -ms-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background: linear-gradient(to right, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0b91c2', endColorstr='#04dce3', GradientType=1 );
background-image: url("images/net-neutrality.png"), -moz-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background-image: url("images/net-neutrality.png"), -webkit-gradient(left top, right top, color-stop(0%, rgba(11,145,194,1)), color-stop(100%, rgba(4,220,227,1)));
background-image: url("images/net-neutrality.png"), -webkit-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background-image: url("images/net-neutrality.png"), -o-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background-image: url("images/net-neutrality.png"), -ms-linear-gradient(left, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
background-image: url("images/net-neutrality.png"), linear-gradient(to right, rgba(11,145,194,1) 0%, rgba(4,220,227,1) 100%);
}
h2{
margin: 1.5em 0 1em 0;
}
h2:first-child{
margin: 0.5em 0 1em 0;
}
#global h2:first-child {
margin-top: 0;
}
......@@ -373,6 +364,27 @@ h1 span {
margin-top: 1em;
}
.navbar-default {
background: #333;
border: none !important;
border-radius: 0;
}
.navbar-default .active a {
color:#555 !important;
}
.navbar-default a{
color:white !important;
}
.navbar-default li:hover{
background:#444 !important;
}
.navbar-default li.active{
background:#555 !important;
}
/* View page */
......@@ -455,7 +467,7 @@ div.comment {
input[type=submit], input[type=button], .button, input[type="text"], textarea, select {
font: 14.4px Helvetica, Arial, "Liberation Sans", FreeSans, sans-serif;
background: #fff;
border: 1px solid #ccc;
/*border: 1px solid #ccc;*/
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
-o-border-radius: 2px;
......@@ -693,7 +705,7 @@ ul.messages {
#map { float: right; }
.logo-org { display: inline-block; margin: 15px; text-align: center; vertical-align: top; background-color: #f9f9f9; }
.logo-org img { height: 80px; }
.logo-org img { height: 80px; margin-bottom:10px}
.dark { background-color: #212121; }
tr.duplicate-status td { background: #aaa !important; }
......@@ -722,6 +734,35 @@ input[type="text"], input[type="textarea"] {
#push { clear: both; }
#wrapper {
max-width: 960px;
margin: auto;
}
#table {
clear: both;
}
div.honeypot {display: none;}
@media screen and (max-width: 700px) {
h1{font-size:30px}
h2{font-size:24px}
h1 span{font-size:16px}
}
@media screen and (max-width: 500px) {
#add-case {
width:100%;
}
#content {
width:100%;
}
}
@media screen and (max-width: 870px) {
.navbar-right input[type="text"]{
width:100px;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -24,12 +24,15 @@ function empty(node) {
// This fucntiosn check the first four forms
function check1st4() {
console.log('Check 1st4');
var resource=$('#id_resource_name').val();
var country=$('#id_country').val();
var op=$('#id_operator').val();
var contract=$('#id_contract').val();
var media=$('#id_media').val();
var contractual=$('#id_contractual').val();
var div=$('#similar_cases');
if(country && op && contract && media) {
if(country && op && contract && media && contractual && resource) {
$.getJSON('/lookup/?country='+country+'&contract='+contract+'&operator='+op+'&media='+media, function(data) {
if (data.length>0) {
div.parent().removeClass('hidden');
......@@ -40,6 +43,7 @@ function check1st4() {
var val=data.pop();
div.append('<li><a href="/view/'+val[0]+'">'+(val[1] || "no resource given")+'</a></li>');
};
console.log("Lookup : " + data);
});
$('#id_comment').parent().parent().show().focus();
$('#id_comment_ifr').focus();
......
This diff is collapsed.
{% load comments i18n %}
{% load bootstrap3 %}
<form class="form" action="{% comment_form_target %}" method="post">
{% csrf_token %}
{% if next %}
<div>
<input type="hidden" name="next" value="{{ next }}"/>
</div>
{% endif %}
{% for field in form %}
{% if field.name == "honeypot" %}
{% bootstrap_field field layout=horizontal form_group_class="honeypot" %}
{% else %}
{% bootstrap_field field layout=horizontal %}
{% endif %}
{% endfor %}
<p><a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /></a>By submitting this comment, I agree that it will be placed under the <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.</p>
<div class="btn-group">
<button type="submit" name="post" class="btn btn-success submit-post"/>{% trans "Post" %}</button>
<button type="submit" name="preview" class="btn btn-default submit-preview"/>{% trans "Preview" %}</button>
</div>
</form>
{% for row in data %}{% for col in row %}"{{ col|safe|addslashes }}"{% if not loop.last %},{% endif %}{% endfor %}
{% endfor %}
"Confirmations","Country","Operator","Contract","Resource","Resource Name","Type","Media","Temporary","Contractual","Contract Excerpt","Loophole","State","Old","Creation Date"
{% autoescape off %} {% for violation in object_list %} "{{ violation.confirmations }}", "{{ violation.country }}", "{{ violation.operator }}", "{{ violation.contract|addslashes }}", "{{ violation.resource|addslashes }}", "{{ violation.resource_name|striptags|addslashes }}", "{{ violation.type|addslashes }}", "{{ violation.media|addslashes }}", "{{ violation.temporary }}", "{{ violation.contractual }}", "{{ violation.contract_excerpt|striptags|addslashes }}", "{{ violation.loophole }}", "{{ violation.state }}", "{{ violation.old }}", "{{ violation.creation_date|date:"DATE_FORMAT" }}"
{% endfor %} {% endautoescape %}
This diff is collapsed.
{% extends "base.html" %}
{% load bt %}
{% load i18n %}
{% block active_tab_cases %}id="current"{% endblock %}
{% block active_tab_cases %}class="active"{% endblock %}
{% block styles %}
<link rel="stylesheet" href="/static/css/map.css" type="text/css" />
{% endblock %}
......@@ -14,80 +14,84 @@
<script type="text/javascript">
var data={% if countryweights %}{{countryweights|safe}}{%else%}[]{% endif %};
var country="{{country}}";
// Table sorter
$.tablesorter.addParser({
// set a unique id
id: 'stateparser',
is: function(s) {
// return false so this parser is not auto detected
return false;
},
format: function(s) {
// format your data for normalization
return s{% for s, translation in status %}.replace(/{{translation}}/,{{forloop.counter}}){% endfor %};
},
// set type, either numeric or text
type: 'numeric'
$('.metoo').submit(function(event) {
event.preventDefault();
var form = $(this);
var pk = $(form).attr("id")
var email = $(form).find("#email-" + pk).val();
$.ajax({
url: "/confirm/" + pk + "/" + email,
success: function(data) {
form.find("div").html(data);
}
});
});
$("#sortedList").tablesorter({
theme: 'dropbox',
});
</script>
{% endblock %}
{%block content%}
{% block content %}
<div id='global'>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% trans "Reported Cases" %}
<div id="map"> </div>
<p>{% trans "This is a list of all reported cases of Net Neutrality violations by our users. Please note that we do not validate the accuracy of these reports before they are published on this list, but rather rely on confirmations and supporting evidence offered by users. If you are subjected to one of the listed restrictions, please confirm it. Cases that are not considered violations of Net Neutrality under our guidelines will be removed or not be validated." %}</p>
</div>
<table class="listing tablesorter zebra-striped" id='sortedlist'>
<thead>
<tr>
<th filter-type='ddl'>{% trans "Status" %}</th>
<th filter-type='ddl'>{% trans "country" %}</th>
<th filter-type='ddl'>{% trans "operator" %}</th>
<th filter-type='ddl'>{% trans "contract" %}</th>
<th filter-type='ddl'>{% trans "resource" %}</th>
<th filter-type='ddl'>{% trans "type" %}</th>
<th filter-type='ddl'>{% trans "fixed / wireless" %}</th>
<th filter='false'>{% trans "confirmations" %}</th>
<th filter='false'></th>
</tr>
</thead>
<tbody>
{% for violation in violations %}
<tr class="{%if violation.state%}{{violation.state}}{%else%}new{%endif%}-status">
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{%if violation.state%}{{violation.state|status}}{%else%}{% trans "New" %}{%endif%}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.country|country }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.operator }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.contract }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.resource_name }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.type|type }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.media|media }}</a></td>
<td>{{ violation.confirmations }}</td>
<td>
<div class="confirm"><a class="button">{% trans "Me too!" %}</a>
<div class="info-validate float-confirm">
<form method="get" action="" id="i{{ violation.pk }}" class="confirm_form" >
<div>
<label>{% trans "In order to <strong>confirm</strong> this report, please enter your email address" %}</label>
<input type="text" name="email" />
<input type="hidden" value="{{ violation.pk }}" />
<input type="submit" value="{% trans "OK" %}" />
<div class="table-responsive" id="table">
<table class="table table-striped table-condensed tablesorter" id='sortedlist'>
<thead>
<tr>
<th>{% trans "Status" %}</th>
<th>{% trans "country" %}</th>
<th>{% trans "operator" %}</th>
<th>{% trans "contract" %}</th>
<th>{% trans "resource" %}</th>
<th>{% trans "type" %}</th>
<th>{% trans "fixed / wireless" %}</th>
<th>{% trans "confirmations" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for violation in violations %}
<tr class="{%if violation.state%}{{violation.state}}{%else%}new{%endif%}-status">
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{%if violation.state%}{{violation.state|status}}{%else%}{% trans "New" %}{%endif%}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.country|country }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.operator }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.contract }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.resource_name }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.type|type }}</a></td>
<td><a class="cell-link" href="{{ violation.get_absolute_url }}">{{ violation.media|media }}</a></td>
<td>{{ violation.confirmations }}</td>
<td>
<button role="button" class="btn btn-default" data-toggle="modal" data-target="#modal-{{ violation.pk }}">{% trans "Me too!" %}</button>
<div class="modal fade" id="modal-{{ violation.pk }}" tabindex="-1" role="dialog" aria-labelledby="modal-label-{{ violation.pk }}" data-backdrop="false">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" class="metoo inline-from" action="/confirm/{{ violation.pk }}" id="i{{ violation.pk }}">
<div class="modal-body">
<div class="form-group">
<label for="list-email-{{ violation.pk }}">{% trans "In order to <strong>confirm</strong> this report, please enter your email address" %}</label>
<input type="text" name="email" id="list-email-{{ violation.pk }}" placeholder="email" />
<input type="hidden" value="{{ violation.pk }}" />
{% csrf_token %}
</div>
<div class="button-box">
<button class="btn btn-success" type="submit">{% trans "OK" %}</button>
<button class="btn btn-danger" type="button" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</form>
</div>
</form>
<input class="cancel-button" type="button" value="{% trans "Close" %}" />
</div>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%endblock%}
{% extends "base.html" %}
{% load bt %}
{% load i18n %}
{% block active_tab_about %}id="current"{% endblock %}
{% block active_tab_about %}class="active"{% endblock %}
{% block content %}
<div id='global'>
<h3>{% trans "On the Internet, <strong>you</strong> decide what you do, right? <em>Not anymore!</em>" %}</h3>
<p>{% trans "Large telecommunication providers want to control what you do online. They want to restrict access to internet telephony in order to force you to use their telephone services. They want to charge you for viewing of videos. They want to prohibit the use of specific software on their networks, or throttle innovative applications such as peer-to-peer filesharing." %}</p>
<p>{% trans "This must stop. We want to ensure that your freedom online is protected and that you decide which content you access, and which applications and services you use. Net neutrality is about your freedom online." %}</p>
<p>{% trans "Violations on Net Neutrality are bad for freedom of communication, freedom of choice, innovation and communication costs. They lead to a situation where the provider can decide what you do online, not you, and this restricts your freedom. It becomes more difficult for innovative companies to offer their services via the internet, because they may be blocked or slowed down by providers. And it leads to higher communication costs because you may have to pay extra to use cheap services such as internet telephony." %}</p>
<p>{% trans "We need to ensure that the Internet remains free, open and accessible for all. This is what Net Neutrality is about. The European Commission and national regulators need to prohibit providers from restricting your online traffic and do so before it is too late. In order to convince them about the urgency of this problem, we aim to create a comprehensive report of Net Neutrality violations in Europe. This website is the way to report these violations." %}</p>
<h3>{% trans "On the Internet, <strong>you</strong> decide what you do, right? <em>Maybe not!</em>" %}</h3>
<p>{% trans "Online companies and telecommunication providers want to control what you do online. For example, they want to restrict access to Internet telephony in order to force you to use the telephone service that they want you to use. They want to charge you extra for watching videos or listening to music, if you choose a service that they are not promoting. They want to prohibit the use of specific software on their networks or throttle innovative applications such as peer-to-peer filesharing. They want people to pay to access your blog." %}</p>
<p>{% trans "This must stop. We want to ensure that your rights and freedoms online are protected and that you decide which content you access and which applications and services you use." %}</p>
<p>{% trans "Net Neutrality violations harm freedom of expression, freedom of information, freedom of choice, innovation, competition, privacy and increase communication costs. " %}</p>
<p>{% trans "We need to ensure that the Internet remains free, open and accessible for all. This is what net neutrality is about. The European Commission and national regulators need to prohibit providers from restricting your online traffic and do so before it is too late. In order to convince them about the urgency of this problem, we aim to create a comprehensive data set of Net Neutrality violations in Europe. " %}</p>
<h3>{% trans "Guidelines for reporting cases" %}</h3>
<p>{% trans "In reporting violations of Net Neutrality on RespectMyNet.eu, please describe only connection issues that are related to traffic discrimination, that is to say cases where Internet access providers discriminate traffic according to the source, destination, type or actual content of the data transmitted over the network (i.e. if your provider blocks traffic coming from YouTube or slows down Usenet traffic). If possible, please verify whether indeed the provider can be blamed: sometimes it is just the server itself which is not working (i.e. if a website is down, this is obviously not the fault of the internet access provider)." %}</p>
<p>{% trans "Internet providers can abuse their control over their network in several ways. For example, they can discriminate between individual applications by throttling, blocking or prioritising them - this is called &ldquo;technical discrimination&rdquo;. They can also discriminate between individual applications by excluding them from your monthly data cap - this is called &ldquo;economic discrimination&rdquo;, which is commonly known as &ldquo;zero-rating&rdquo;. A mixture of both types of discrimination is usually encountered in the provision of abusive &ldquo;Specialised Services&rdquo;, which can turn into paid-fast lanes." %}</p>
<p>{% trans "When reporting economic net neutrality violations through RespectMyNet.eu, please provide us with a description of the practice of your ISP. We need to know what services are not counted towards your monthly data cap and what happens with your Internet connection if your data cap is exceeded. By providing links to the relevant offerings, advertisement material and terms-of-service of the product, you are providing us with crucial evidence to present to the regulators. If you are not sure on how to classify a violation that you encounter, just leave the relevant fields empty. Submissions can be discussed and modified once they are in the system." %}</p>
<p>{% trans "When reporting technical net neutrality violations through RespectMyNet.eu, please describe only connection issues that are related to traffic discrimination, that is to say cases where Internet access providers discriminate between traffic according to the source, destination, type or actual content of the data transmitted over the network (i.e. if your provider blocks traffic coming from YouTube or slows down Usenet traffic). If possible, please verify whether the provider is the one to be blamed: sometimes it is just the server itself which is not working (i.e. if a website is down, this is obviously not the fault of the Internet access provider)." %}</p>
<p>{% trans "Sample of common ISP issues that DO NOT require a formal complaint (from: <a href='http://www.ispreview.co.uk/new/complain/complain.shtml'>http://www.ispreview.co.uk/new/complain/complain.shtml</a>)" %}</p>
<ul class="with-disc">
<li>
<p>{% trans "<b>Brief and Uncommon Critical Service (Email, Website Browsing etc.) Outages.</b>" %}</p>
<p>{% trans "Sadly, ISPs experience occasional problems with online services, such as email access. These are usually resolved after a few minutes or hours, an only very occasionally will they last longer than a day. Don't get too frustrated, inform them of your problem and allow some time for it to be resolved." %}</p>
<p>{% trans "Unfortunately, ISPs experience occasional problems with online services, such as email access. These are usually resolved after a few minutes or hours, and only very occasionally will they last longer than a day. Don't get too frustrated, inform them of your problem and allow some time for it to be resolved." %}</p>
</li>
<li>