Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
memopol
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
TAlone
memopol
Commits
ee22f2c4
Commit
ee22f2c4
authored
Jul 01, 2015
by
Arnaud Fabre
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
votes / legislature models
parent
e85f36d7
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
395 additions
and
87 deletions
+395
-87
.gitignore
.gitignore
+0
-6
legislature/migrations/0001_initial.py
legislature/migrations/0001_initial.py
+0
-36
legislature/migrations/__init__.py
legislature/migrations/__init__.py
+0
-0
legislature/models.py
legislature/models.py
+56
-30
legislature/templates/legislature/representative_view.haml
legislature/templates/legislature/representative_view.haml
+3
-0
legislature/views.py
legislature/views.py
+2
-2
representatives
representatives
+1
-0
votes/admin.py
votes/admin.py
+32
-3
votes/admin_views.py
votes/admin_views.py
+2
-3
votes/admin_views_flymake.py
votes/admin_views_flymake.py
+117
-0
votes/migrations/0002_auto_20150616_1516.py
votes/migrations/0002_auto_20150616_1516.py
+26
-0
votes/migrations/0003_auto_20150616_1523.py
votes/migrations/0003_auto_20150616_1523.py
+26
-0
votes/migrations/0004_auto_20150616_1527.py
votes/migrations/0004_auto_20150616_1527.py
+21
-0
votes/migrations/0005_auto_20150617_1243.py
votes/migrations/0005_auto_20150617_1243.py
+26
-0
votes/models.py
votes/models.py
+38
-6
votes/tasks.py
votes/tasks.py
+44
-0
votes/templates/votes/admin/import.haml
votes/templates/votes/admin/import.haml
+1
-1
No files found.
.gitignore
View file @
ee22f2c4
# Django apps
compotista_django-representatives
representatives
chronograph
*.sqlite3
# SASS Cache
...
...
legislature/migrations/0001_initial.py
deleted
100644 → 0
View file @
e85f36d7
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'representatives'
,
'0001_initial'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'MemopolGroup'
,
fields
=
[
(
'group'
,
models
.
OneToOneField
(
parent_link
=
True
,
primary_key
=
True
,
serialize
=
False
,
to
=
'representatives.Group'
)),
(
'active'
,
models
.
BooleanField
(
default
=
False
)),
],
options
=
{
},
bases
=
(
'representatives.group'
,),
),
migrations
.
CreateModel
(
name
=
'MemopolRepresentative'
,
fields
=
[
(
'representative_ptr'
,
models
.
OneToOneField
(
parent_link
=
True
,
auto_created
=
True
,
primary_key
=
True
,
serialize
=
False
,
to
=
'representatives.Representative'
)),
(
'representative_remote_id'
,
models
.
CharField
(
unique
=
True
,
max_length
=
255
)),
(
'score'
,
models
.
IntegerField
(
default
=
0
)),
(
'country'
,
models
.
ForeignKey
(
to
=
'representatives.Country'
,
null
=
True
)),
],
options
=
{
},
bases
=
(
'representatives.representative'
,),
),
]
legislature/migrations/__init__.py
deleted
100644 → 0
View file @
e85f36d7
legislature/models.py
View file @
ee22f2c4
...
...
@@ -22,11 +22,29 @@ from datetime import datetime
from
django.db
import
models
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
django.utils.functional
import
cached_property
from
representatives.models
import
Representative
,
Group
,
Country
from
representatives.models
import
Representative
,
Mandate
,
Country
from
representatives_votes.models
import
Vote
from
core.utils
import
create_child_instance_from_parent
class
MemopolRepresentative
(
Representative
):
class
MemopolRepresentative
(
models
.
Model
):
# We should link a memopol representative to a representative based
# on the remote_id attribute
parent_identifier
=
'remote_id'
child_parent_identifier
=
'representative_remote_id'
representative
=
models
.
OneToOneField
(
Representative
,
parent_link
=
True
,
related_name
=
'extra'
,
null
=
True
,
on_delete
=
models
.
SET_NULL
)
representative_remote_id
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
country
=
models
.
ForeignKey
(
Country
,
null
=
True
)
...
...
@@ -34,14 +52,23 @@ class MemopolRepresentative(Representative):
def
update_score
(
self
):
score
=
0
for
vote
in
self
.
representative
.
votes
.
all
():
proposal
=
vote
.
m_proposal
if
proposal
.
recommendation
:
recommendation
=
proposal
.
recommendation
if
vote
.
position
!=
recommendation
.
recommendation
:
score
-=
recommendation
.
weight
else
:
score
+=
recommendation
.
weight
for
vote
in
self
.
votes
.
all
():
proposal
=
vote
.
proposal
try
:
if
proposal
.
recommendation
:
recommendation
=
proposal
.
recommendation
if
(
vote
.
position
!=
recommendation
.
recommendation
and
(
vote
.
position
==
'abstain'
or
recommendation
.
recommendation
==
'abstain'
)):
score
-=
(
recommendation
.
weight
/
2
)
elif
vote
.
position
!=
recommendation
.
recommendation
:
score
-=
recommendation
.
weight
else
:
score
+=
recommendation
.
weight
except
Exception
:
pass
self
.
score
=
score
self
.
save
()
...
...
@@ -66,11 +93,11 @@ class MemopolRepresentative(Representative):
self
.
save
()
# @
property
#
def votes(self):
#
return Vote.objects.filter(
#
representative_remote_id = self.remote_id
#
)
@
cached_
property
def
votes
(
self
):
return
Vote
.
objects
.
filter
(
representative_remote_id
=
self
.
remote_id
)
def
active_mandates
(
self
):
return
self
.
mandates
.
filter
(
...
...
@@ -88,20 +115,19 @@ class MemopolRepresentative(Representative):
group__kind
=
'group'
)
class
MemopolGroup
(
Group
):
group
=
models
.
OneToOneField
(
Group
,
parent_link
=
True
)
active
=
models
.
BooleanField
(
default
=
False
)
def
update_active
(
self
):
self
.
active
=
False
for
mandate
in
self
.
mandates
.
all
():
if
mandate
.
end_date
>
datetime
.
date
(
datetime
.
now
()):
self
.
active
=
True
break
self
.
save
()
@
receiver
(
post_save
,
sender
=
Representative
)
def
create_memopolrepresentative_from_representative
(
instance
,
**
kwargs
):
# create_child_instance_from_parent(MemopolRepresentative, instance)
pass
@
receiver
(
post_save
,
sender
=
Mandate
)
def
update_memopolrepresentative_country
(
instance
,
created
,
**
kwargs
):
return
if
not
created
:
return
# Update representative country
if
instance
.
group
.
kind
==
'country'
and
instance
.
representative
.
extra
.
country
==
None
:
instance
.
representative
.
extra
.
update_country
()
legislature/templates/legislature/representative_view.haml
View file @
ee22f2c4
...
...
@@ -9,6 +9,9 @@
%h1
=
representative
.
full_name
%h2
SCORE : {{ representative.extra.score }}
%p
%strong
%a
{
:href
=>
"{{ representative.current_group_mandate|by_group_url }}"
}
...
...
legislature/views.py
View file @
ee22f2c4
...
...
@@ -132,8 +132,8 @@ def _render_list(request, representative_list, num_by_page=30):
def
groups_by_kind
(
request
,
kind
):
groups
=
Group
.
objects
.
filter
(
kind
=
kind
,
m
emopolgroup__active
=
True
)
m
andates__end_date__gte
=
datetime
.
now
()
)
.
distinct
().
order_by
(
'name'
)
return
render
(
request
,
...
...
representatives
0 → 120000
View file @
ee22f2c4
../django-representatives/representatives
\ No newline at end of file
votes/admin.py
View file @
ee22f2c4
...
...
@@ -20,16 +20,45 @@
from
__future__
import
absolute_import
from
django.contrib
import
admin
from
django.core.urlresolvers
import
reverse
from
.admin_views
import
import_vote_with_recommendation
,
import_vote
from
.models
import
Recommendation
,
MemopolDossier
from
.admin_import_vote
import
import_vote_with_recommendation
,
import_vote
from
.models
import
Recommendation
admin
.
site
.
register_view
(
'import_vote'
,
view
=
import_vote
)
admin
.
site
.
register_view
(
'import_vote_with_recommendation'
,
view
=
import_vote_with_recommendation
)
def
link_to_edit
(
obj
,
field
):
try
:
related_obj
=
getattr
(
obj
,
field
)
url
=
reverse
(
'admin:{}_{}_change'
.
format
(
related_obj
.
_meta
.
app_label
,
related_obj
.
_meta
.
object_name
.
lower
()
),
args
=
(
related_obj
.
pk
,)
)
return
' <strong><a href="{url}">{obj}</a></strong>'
.
format
(
url
=
url
,
obj
=
related_obj
)
except
:
return
'???'
class
MemopolDossierAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'name'
,
'dossier'
)
search_fields
=
(
'name'
,)
class
RecommendationsAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'title'
,
'recommendation'
,
'proposal'
,
'weight'
)
def
link_to_proposal
(
self
):
return
link_to_edit
(
self
,
'proposal'
)
link_to_proposal
.
allow_tags
=
True
list_display
=
(
'id'
,
'title'
,
link_to_proposal
,
'recommendation'
,
'weight'
)
search_fields
=
(
'title'
,
'recommendation'
,
'proposal'
)
admin
.
site
.
register
(
MemopolDossier
,
MemopolDossierAdmin
)
admin
.
site
.
register
(
Recommendation
,
RecommendationsAdmin
)
votes/admin_
import_vote
.py
→
votes/admin_
views
.py
View file @
ee22f2c4
...
...
@@ -106,10 +106,9 @@ def import_vote(request):
else
:
proposal_id
=
request
.
GET
.
get
(
'import'
,
None
)
if
proposal_id
:
api_url
=
'{}/api/proposals/{}'
.
format
(
toutatis_server
,
proposal_id
)
proposal
=
requests
.
get
(
api_url
).
json
()
#
api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
#
proposal = requests.get(api_url).json()
call_command
(
'import_proposal_from_toutatis'
,
proposal_id
,
interactive
=
False
)
# call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
return
redirect
(
'/admin/'
)
...
...
votes/admin_views_flymake.py
0 → 100644
View file @
ee22f2c4
# coding: utf-8
# This file is part of memopol.
#
# memopol is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or any later version.
#
# memopol is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU General Affero Public
# License along with django-representatives.
# If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
from
__future__
import
absolute_import
from
django.conf
import
settings
from
django.shortcuts
import
render
,
redirect
from
django
import
forms
from
django.core.management
import
call_command
import
requests
from
representatives_votes.models
import
Proposal
from
.forms
import
RecommendationForm
class
SearchForm
(
forms
.
Form
):
query
=
forms
.
CharField
(
label
=
'Search'
,
max_length
=
100
)
def
import_vote_with_recommendation
(
request
):
context
=
{}
toutatis_server
=
getattr
(
settings
,
'TOUTATIS_SERVER'
,
'http://toutatis.mm.staz.be'
)
if
request
.
method
==
'POST'
and
'search'
in
request
.
POST
:
form
=
SearchForm
(
request
.
POST
)
if
form
.
is_valid
():
query
=
form
.
cleaned_data
[
'query'
]
context
[
'api_url'
]
=
'{}/api/proposals/?search={}&limit=1000'
.
format
(
toutatis_server
,
query
)
r
=
requests
.
get
(
context
[
'api_url'
])
context
[
'results'
]
=
r
.
json
()
elif
request
.
method
==
'POST'
and
'create_recommendation'
in
request
.
POST
:
form
=
RecommendationForm
(
data
=
request
.
POST
)
if
form
.
is_valid
():
# First import proposal
proposal_id
=
int
(
request
.
POST
[
'proposal_id'
])
api_url
=
'{}/api/proposals/{}'
.
format
(
toutatis_server
,
proposal_id
)
proposal
=
requests
.
get
(
api_url
).
json
()
call_command
(
'import_proposal_from_toutatis'
,
proposal_id
,
interactive
=
False
)
# call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
memopol_proposal
=
Proposal
.
objects
.
get
(
title
=
proposal
[
'title'
],
datetime
=
proposal
[
'datetime'
],
kind
=
proposal
[
'kind'
],
)
recommendation
=
form
.
save
(
commit
=
False
)
recommendation
.
proposal
=
memopol_proposal
recommendation
.
save
()
return
redirect
(
'/admin/votes/recommendation/'
)
else
:
proposal_id
=
request
.
GET
.
get
(
'import'
,
None
)
if
proposal_id
:
api_url
=
'{}/api/proposals/{}'
.
format
(
toutatis_server
,
proposal_id
)
proposal
=
requests
.
get
(
api_url
).
json
()
context
[
'recommendation_proposal_title'
]
=
proposal
[
'title'
]
context
[
'recommendation_proposal_dossier_title'
]
=
proposal
[
'dossier_title'
]
context
[
'recommendation_proposal_id'
]
=
proposal_id
context
[
'recommendation_form'
]
=
RecommendationForm
()
form
=
SearchForm
()
context
[
'form'
]
=
form
return
render
(
request
,
'votes/admin/import.html'
,
context
)
def
import_vote
(
request
):
context
=
{}
toutatis_server
=
getattr
(
settings
,
'TOUTATIS_SERVER'
,
'http://toutatis.mm.staz.be'
)
if
request
.
method
==
'POST'
and
'search'
in
request
.
POST
:
print
(
request
.
POST
)
form
=
SearchForm
(
request
.
POST
)
if
form
.
is_valid
():
query
=
form
.
cleaned_data
[
'query'
]
context
[
'api_url'
]
=
'{}/api/proposals/?search={}&limit=1000'
.
format
(
toutatis_server
,
query
)
r
=
requests
.
get
(
context
[
'api_url'
])
context
[
'results'
]
=
r
.
json
()
else
:
proposal_id
=
request
.
GET
.
get
(
'import'
,
None
)
if
proposal_id
:
# api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
# proposal = requests.get(api_url).json()
call_command
(
'import_proposal_from_toutatis'
,
proposal_id
,
interactive
=
False
)
# call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
return
redirect
(
'/admin/'
)
form
=
SearchForm
()
context
[
'form'
]
=
form
return
render
(
request
,
'votes/admin/import.html'
,
context
)
votes/migrations/0002_auto_20150616_1516.py
0 → 100644
View file @
ee22f2c4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
import
core.fields
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'representatives_votes'
,
'0002_auto_20150616_1249'
),
(
'votes'
,
'0001_initial'
),
]
operations
=
[
migrations
.
RemoveField
(
model_name
=
'memopoldossier'
,
name
=
'dossier_ptr'
,
),
migrations
.
AddField
(
model_name
=
'memopoldossier'
,
name
=
'dossier'
,
field
=
core
.
fields
.
AutoOneToOneField
(
primary_key
=
True
,
default
=
0
,
serialize
=
False
,
to
=
'representatives_votes.Dossier'
),
preserve_default
=
False
,
),
]
votes/migrations/0003_auto_20150616_1523.py
0 → 100644
View file @
ee22f2c4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'votes'
,
'0002_auto_20150616_1516'
),
]
operations
=
[
migrations
.
AlterField
(
model_name
=
'memopoldossier'
,
name
=
'description'
,
field
=
models
.
TextField
(
default
=
b
''
,
blank
=
True
),
preserve_default
=
True
,
),
migrations
.
AlterField
(
model_name
=
'memopoldossier'
,
name
=
'name'
,
field
=
models
.
CharField
(
default
=
b
''
,
max_length
=
1000
,
blank
=
True
),
preserve_default
=
True
,
),
]
votes/migrations/0004_auto_20150616_1527.py
0 → 100644
View file @
ee22f2c4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
import
core.fields
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'votes'
,
'0003_auto_20150616_1523'
),
]
operations
=
[
migrations
.
AlterField
(
model_name
=
'memopoldossier'
,
name
=
'dossier'
,
field
=
core
.
fields
.
AutoOneToOneField
(
parent_link
=
True
,
related_name
=
'extra'
,
primary_key
=
True
,
serialize
=
False
,
to
=
'representatives_votes.Dossier'
),
preserve_default
=
True
,
),
]
votes/migrations/0005_auto_20150617_1243.py
0 → 100644
View file @
ee22f2c4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
models
,
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'votes'
,
'0004_auto_20150616_1527'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'memopoldossier'
,
name
=
'dossier_reference'
,
field
=
models
.
CharField
(
default
=
''
,
max_length
=
200
),
preserve_default
=
False
,
),
migrations
.
AlterField
(
model_name
=
'memopoldossier'
,
name
=
'dossier'
,
field
=
models
.
OneToOneField
(
parent_link
=
True
,
related_name
=
'extra'
,
primary_key
=
True
,
serialize
=
False
,
to
=
'representatives_votes.Dossier'
),
preserve_default
=
True
,
),
]
votes/models.py
View file @
ee22f2c4
...
...
@@ -19,16 +19,21 @@
# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
from
django.db
import
models
from
django.utils.functional
import
cached_property
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
representatives_votes.models
import
Vote
,
Proposal
,
Dossier
from
legislature.models
import
MemopolRepresentative
from
core.utils
import
create_child_instance_from_parent
from
.tasks
import
update_representatives_score_for_proposal
class
Recommendation
(
models
.
Model
):
SCORE_TABLE
=
{
(
'abstain'
,
'abstain'
):
1
,
(
'abstain'
,
'for'
):
-
0.5
,
(
'abstain'
,
'against'
):
-
0.5
,
}
VOTECHOICES
=
(
...
...
@@ -36,27 +41,54 @@ class Recommendation(models.Model):
(
'for'
,
'for'
),
(
'against'
,
'against'
)
)
proposal
=
models
.
OneToOneField
(
Proposal
,
related_name
=
'recommendation'
)
recommendation
=
models
.
CharField
(
max_length
=
10
,
choices
=
VOTECHOICES
)
title
=
models
.
CharField
(
max_length
=
1000
,
blank
=
True
)
description
=
models
.
TextField
(
blank
=
True
)
weight
=
models
.
IntegerField
(
default
=
0
)
@
receiver
(
post_save
,
sender
=
Recommendation
)
def
update_score
(
instance
,
**
kwargs
):
update_representatives_score_for_proposal
(
instance
.
proposal
)
class
MemopolDossier
(
Dossier
):
name
=
models
.
CharField
(
max_length
=
1000
)
description
=
models
.
TextField
(
blank
=
True
)
parent_identifier
=
'reference'
child_parent_identifier
=
'dossier_reference'
dossier
=
models
.
OneToOneField
(
Dossier
,
primary_key
=
True
,
parent_link
=
True
,
related_name
=
'extra'
)
dossier_reference
=
models
.
CharField
(
max_length
=
200
)
name
=
models
.
CharField
(
max_length
=
1000
,
blank
=
True
,
default
=
''
)
description
=
models
.
TextField
(
blank
=
True
,
default
=
''
)
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
name
:
self
.
name
=
self
.
dossier
.
title
return
super
(
MemopolDossier
,
self
).
save
(
*
args
,
**
kwargs
)
@
receiver
(
post_save
,
sender
=
Dossier
)
def
create_memopolrepresentative_from_representative
(
instance
,
**
kwargs
):
create_child_instance_from_parent
(
MemopolDossier
,
instance
)
class
MemopolVote
(
Vote
):
class
Meta
:
proxy
=
True
@
property
@
cached_
property
def
representative
(
self
):
return
MemopolRepresentative
.
objects
.
get
(
remote_id
=
self
.
representative_remote_id
...
...
votes/tasks.py
0 → 100644
View file @
ee22f2c4
# coding: utf-8
# This file is part of memopol.
#
# memopol is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or any later version.
#
# memopol is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU General Affero Public
# License along with django-representatives.
# If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
from
__future__
import
absolute_import
from
celery
import
shared_task
from
legislature.models
import
MemopolRepresentative
from
django.db.models
import
get_model
@
shared_task
def
update_representatives_score
():
'''
Update score for all representatives
'''
for
representative
in
MemopolRepresentative
.
objects
.
all
():
representative
.
update_score
()
@
shared_task
def
update_representatives_score_for_proposal
(
proposal
):
'''
Update score for representatives that have votes for proposal
'''
MemopolVote
=
get_model
(
'votes'
,
'MemopolVote'
)
for
vote
in
MemopolVote
.
objects
.
filter
(
proposal_id
=
proposal
.
id
):
# Extra is the MemopolRepresentative object
vote
.
representative
.
extra
.
update_score
()
votes/templates/votes/admin/import.haml
View file @
ee22f2c4
...
...
@@ -8,7 +8,7 @@
%form
{
:action
=>
''
,
:method
=>
'post'
}
-
csrf_token
{{ form }}
.
{{ form }}
%input
{
:type
=>
'submit'
,
:value
=>
'Search'
,
:name
=>
'search'
}
-
if
results
...
...
Write
Preview