Skip to content

Commit 042d6bf

Browse files
committed
Merge branch 'master' into django-1.5
Conflicts: requirements.txt
2 parents a172423 + d167cb2 commit 042d6bf

File tree

286 files changed

+6941
-3091
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

286 files changed

+6941
-3091
lines changed

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ local_*.py
77
data/
88
solr/
99
files/
10-
artwork/
11-
documents/
1210
.coverage
1311
*/_build/
14-
froide/htmlcov/
12+
htmlcov/
13+
public/

.travis.yml

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1+
---
12
language: python
23
python:
3-
- "2.6"
4-
- "2.7"
4+
- '2.7'
5+
- '2.6'
56
services:
6-
- elasticsearch
7+
- elasticsearch
78
before_install:
8-
- export PIP_USE_MIRRORS=true
9-
- export PIP_INDEX_URL=https://simple.crate.io/
10-
- "export DISPLAY=:99.0"
11-
- "sh -e /etc/init.d/xvfb start"
9+
- export PIP_USE_MIRRORS=true
10+
- export PIP_INDEX_URL=https://simple.crate.io/
11+
- phantomjs --version
12+
- phantomjs --webdriver= &
1213
install:
13-
- pip install -e .
14-
- pip install -r requirements-test.txt
14+
- pip install https://github.com/stefanw/terrarium/archive/develop.zip boto
15+
- terrarium key requirements-test.txt
16+
- terrarium --virtualenv-log-level 20 --pip-log-level 20 --target froideenv --remote-key-format "u/stwe/froideenv/%(arch)s-%(python_vmajor)s.%(python_vminor)s-%(digest)s" install requirements-test.txt || (wget "http://resources.opendatalabs.org.s3.amazonaws.com/u/stwe/froideenv/`terrarium key requirements-test.txt`" ; terrarium --pip-log-level 20 --virtualenv-log-level 20 --no-upload --s3-bucket "" --target froideenv --storage-dir . install requirements-test.txt)
17+
- source froideenv/bin/activate
18+
- which python
19+
- pip install -e .
1520
script:
16-
- make test
21+
- make test
1722
branches:
1823
only:
19-
- master
24+
- master
25+
env:
26+
global:
27+
- S3_BUCKET=resources.opendatalabs.org
28+
- S3_ACCESS_KEY=AKIAIY6JZKLHFPB3KTQA
29+
- secure: "B0JCveS4EwWZybclbEpFN1e/Am6uTTBU9qFJTFs0fyj8LBmdUl14YcWK+24O\nBiX9C3QUsfzHP5SoMEo3GgYZzLMIKRX8wr/UdspyW6sBh8VyPqfaanWys9xn\nGETJBJA30cYpUEwfEsNko/xz7TpW3dfP6O2hweSFuPo/Uzjt1tg="

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export DJANGO_SETTINGS_MODULE=froide.test_settings
22

33
test:
44
pep8 --ignore=E501,E124,E126,E127,E128 --exclude=migrations froide
5-
coverage run --branch --source=froide `which django-admin.py` test froide.account.tests froide.foirequest.tests froide.foirequestfollower.tests froide.frontpage.tests froide.publicbody.tests
5+
coverage run --branch --source=froide `which django-admin.py` test froide
66
coverage report --omit="*/migrations/*"

README.mkd renamed to README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Froide
22
======
33

4-
[![Build Status](https://secure.travis-ci.org/stefanw/froide.png)](http://travis-ci.org/#!/stefanw/froide)
4+
[![Build Status](https://travis-ci.org/stefanw/froide.png?branch=master)](https://travis-ci.org/stefanw/froide)
55

66

77
Froide is a Freedom Of Information Portal written in Python using Django 1.4.3.

docs/api.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
==========
2+
Froide API
3+
==========
4+
5+
Froide has a RESTful API that allows structured access to the content
6+
inside your Froide instance.
7+
8+
The Froide API is available at `/api/v1/` and the interactive Froide API documentation is available at `/api/v1/docs/`.
9+
10+
There are additional search endpoints for Public Bodies and FOI Requests at `/api/v1/publicbody/search/` and `/api/v1/request/search/` respectively. Use `q` as the query parameter in a GET request.
11+
12+
GET requests do not need to be authenticated. POST, PUT and DELETE requests have to either carry a valid session cookie and a CSRF token or provide username (you find your username on your profile) and password via Basic Authentication.

docs/gettingstarted.rst

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,26 @@ Install the requirements inside the virtual env with `pip`::
2828

2929
If only your global `pip` is available, run `easy_install pip`. The dependency installation takes a couple of seconds, but after that everything is in place.
3030

31-
Copy `local_settings.py.example` to `local_settings.py`::
31+
Copy `custom_settings.py.example` to `custom_settings.py`::
3232

3333
cd froide
34-
cp local_settings.py.example local_settings.py
34+
cp custom_settings.py.example custom_settings.py
35+
36+
The development environment uses SQLite. You can override the default Django settings values for froide in your `custom_settings.py`, if you want, but you don't have to.
37+
38+
Your `custom_settings.py` file (you can name it differently) is your Django settings file that you must give on each Django manage.py command. You can also set the `DJANGO_SETTINGS_MODULE` environment variable.
3539

36-
The development environment uses SQLite. You can change that in `local_settings.py`, if you want, but you don't have to.
3740
Sync and migrate and *do NOT* create a superuser just yet::
3841

39-
python manage.py syncdb --noinput --migrate
42+
python manage.py syncdb --noinput --migrate --settings=froide.custom_settings
4043

4144
Now you can create a superuser account::
4245

43-
python manage.py createsuperuser
46+
python manage.py createsuperuser --settings=froide.custom_settings
4447

4548
That's it for a setup that basically works. Run this::
4649

47-
python manage.py runserver
50+
python manage.py runserver --settings=froide.custom_settings
4851

4952
and go to `http://localhost:8000 <http://localhost:8000>`_. You should
5053
be greeted by a working Froide installation. It doesn't have any data
@@ -55,13 +58,13 @@ For more information on the different models you find in the admin visit :doc:`m
5558
Run tests
5659
---------
5760

58-
Froide has a test suite. Copy `test_settings.py.example` to `test_settings.py`. `test_settings.py` does not import your `local_settings.py` changes.
61+
Froide has a test suite. Copy `test_settings.py.example` to `test_settings.py`. `test_settings.py` does not use your `custom_settings.py` changes.
5962

60-
You can then run the shell script for tests::
63+
You can then run for tests::
6164

62-
sh runtests.sh
65+
make test
6366

64-
This also does timing and a test coverage analysis that you can then
67+
This also does test coverage analysis that you can then
6568
find at `htmlcov/index.html`.
6669
Note some tests will not work without a search engine like solr running.
6770

docs/importpublicbodies.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
=======================
2+
Importing Public Bodies
3+
=======================
4+
5+
While it is possible to create public bodies individually through the admin
6+
interface, it might be more advisable to scrape a list from a website or
7+
crowdsource them with other people in a Google doc. You can then import these
8+
public bodies as a CSV file into Froide.
9+
10+
The format of the CSV file should be like `in this Google Docs Spreadsheet <https://docs.google.com/spreadsheet/ccc?key=0AhDkodM9ozpddGNTaGJoa203aEJaRXVfM0Q0d1RjNUE#gid=0>`_. You could for example copy it and either fill in public bodies collaboratively or programmatically.
11+
12+
Format
13+
------
14+
15+
The format is quite simple.
16+
17+
name
18+
(required) The name of the public body.
19+
email
20+
(optional) If you give no email, users will not be able to make requests to this public body. You can fill in email addresses later through the admin.
21+
jurisdiction__slug
22+
(required) Give the slug of the jurisdiction this public body belongs to.
23+
other_names
24+
(optional) Possible other, alternative names for the public body separated by commas.
25+
description
26+
(optional) A text description of the public body.
27+
topic__slug
28+
(optional) Slug for this public body's topic. If not given, the public body topic with the highest rank attribute will be chosen.
29+
url
30+
(optional) Website for this public body.
31+
parent__name
32+
(optional) if this public body has a parent, give it's name here. The parent must be specified before in the CSV file.
33+
classification
34+
(required) Give a classification (e.g. "ministry"). If you don't want have the information, just fill in something general like "public body".
35+
contact
36+
(optional) Contact information apart from post address and email. E.g. phone or fax number. May contain line breaks.
37+
address
38+
(optional) Postal address of this public body.
39+
website_dump
40+
(optional) Any further text that can be used to described this public body. This is used for search indexing and will not be displayed publicly.
41+
request_note
42+
(optional) Display this text for this public body when making a request to it.
43+
44+
If during import a public body with the same slug is found, it is skipped and not overwritten.
45+
46+
Importing through Admin
47+
-----------------------
48+
49+
The admin interface that lists public bodies has an import form at the very bottom of the page. Give a HTTP or HTTPS url of your CSV file and press the import button. The file will be downloaded and imported. Any errors will be shown to you.
50+
51+
52+
Importing via command line
53+
--------------------------
54+
55+
The management command `import_csv` takes a URL or a path to a CSV file::
56+
57+
python manage.py import_csv public_bodies.csv --settings=froide.custom_settings
58+
59+

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ Freedom of Information Portal.
1717

1818
about
1919
gettingstarted
20+
importpublicbodies
2021
models
2122
configuration
23+
api
2224
development
2325
productionsetup
2426

froide/account/models.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import hmac
22
import re
33

4-
from django.db import models
4+
from django.db import models, transaction, IntegrityError
55
from django.conf import settings
66
from django import dispatch
77
from django.utils.translation import ugettext_lazy as _
@@ -169,25 +169,40 @@ def create_user(cls, **data):
169169
last_name=data['last_name'],
170170
email=data['user_email'])
171171
username_base = cls.get_username_base(user.first_name, user.last_name)
172-
count = 0
173-
while True:
174-
if count:
175-
username = "%s_%s" % (username_base, count)
176-
else:
177-
username = username_base
178-
try:
179-
User.objects.get(username=username)
180-
except User.DoesNotExist:
181-
break
182-
count += 1
183-
user.username = username
172+
184173
user.is_active = False
185174
if "password" in data:
186175
password = data['password']
187176
else:
188177
password = User.objects.make_random_password()
189178
user.set_password(password)
190-
user.save()
179+
180+
# ensure username is unique
181+
while True:
182+
username = username_base
183+
first_round = True
184+
count = 0
185+
postfix = ""
186+
with transaction.commit_manually():
187+
try:
188+
while True:
189+
if not first_round:
190+
postfix = "_%d" % count
191+
if not User.objects.filter(username=username + postfix).exists():
192+
break
193+
if first_round:
194+
first_round = False
195+
count = User.objects.filter(username__startswith=username).count()
196+
else:
197+
count += 1
198+
user.username = username + postfix
199+
user.save()
200+
except IntegrityError:
201+
transaction.rollback()
202+
else:
203+
transaction.commit()
204+
break
205+
191206
profile = user.get_profile()
192207
for key in data:
193208
if key in ('first_name', 'last_name', 'user_email', 'password'):

froide/local_settings.py.example renamed to froide/custom_settings.py.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# -*- coding: utf-8 -*-
22
import re
33

4+
from .settings import * # noqa
5+
6+
# Add your theme to the top of installed apps
7+
# INSTALLED_APPS = ['your_theme'] + INSTALLED_APPS
8+
9+
410
DEBUG = True
511
TEMPLATE_DEBUG = True
612

@@ -39,6 +45,7 @@ CACHES = {
3945
}
4046

4147
SITE_NAME = "Your Site Name"
48+
SITE_EMAIL = "[email protected]"
4249
SITE_URL = 'http://localhost:8000'
4350

4451
rec = re.compile
@@ -52,6 +59,11 @@ SECRET_URLS = {
5259
"databrows": "databrowse"
5360
}
5461

62+
# Possibly set this for https deployments
63+
# see https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header
64+
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
65+
66+
5567
FROIDE_CONFIG = {
5668
"create_new_publicbody": True,
5769
"publicbody_empty": True,

froide/foirequest/admin.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from taggit.utils import parse_tags
1010

1111
from froide.foirequest.models import (FoiRequest, FoiMessage,
12-
FoiAttachment, FoiEvent, PublicBodySuggestion)
12+
FoiAttachment, FoiEvent, PublicBodySuggestion,
13+
DeferredMessage)
1314
from froide.foirequest.tasks import count_same_foirequests
1415

1516

@@ -173,8 +174,59 @@ class PublicBodySuggestionAdmin(admin.ModelAdmin):
173174
raw_id_fields = ('request', 'public_body', 'user')
174175

175176

177+
class DeferredMessageAdmin(admin.ModelAdmin):
178+
model = DeferredMessage
179+
180+
list_display = ('recipient', 'timestamp',)
181+
raw_id_fields = ('request',)
182+
actions = ['redeliver']
183+
184+
def redeliver(self, request, queryset):
185+
"""
186+
Redeliver undelivered mails
187+
188+
"""
189+
opts = self.model._meta
190+
# Check that the user has change permission for the actual model
191+
if not self.has_change_permission(request):
192+
raise PermissionDenied
193+
194+
# User has already chosen the other req
195+
if request.POST.get('req_id'):
196+
req_id = int(request.POST.get('req_id'))
197+
try:
198+
req = FoiRequest.objects.get(id=req_id)
199+
except (ValueError, FoiRequest.DoesNotExist,):
200+
raise PermissionDenied
201+
202+
for deferred in queryset:
203+
deferred.redeliver(req)
204+
205+
self.message_user(request, _("Successfully triggered redelivery."))
206+
207+
return None
208+
209+
db = router.db_for_write(self.model)
210+
context = {
211+
'opts': opts,
212+
'queryset': queryset,
213+
'media': self.media,
214+
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
215+
'req_widget': mark_safe(admin.widgets.ForeignKeyRawIdWidget(
216+
self.model._meta.get_field(
217+
'request').rel, self.admin_site, using=db).render(
218+
'req_id', None).replace('../../..', '../..')),
219+
'applabel': opts.app_label
220+
}
221+
222+
# Display the confirmation page
223+
return TemplateResponse(request, 'foirequest/admin_redeliver.html',
224+
context, current_app=self.admin_site.name)
225+
redeliver.short_description = _("Redeliver to...")
226+
176227
admin.site.register(FoiRequest, FoiRequestAdmin)
177228
admin.site.register(FoiMessage, FoiMessageAdmin)
178229
admin.site.register(FoiAttachment, FoiAttachmentAdmin)
179230
admin.site.register(FoiEvent, FoiEventAdmin)
180231
admin.site.register(PublicBodySuggestion, PublicBodySuggestionAdmin)
232+
admin.site.register(DeferredMessage, DeferredMessageAdmin)

0 commit comments

Comments
 (0)