Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
luxcem
memopol
Commits
22643a22
Commit
22643a22
authored
Oct 12, 2016
by
Nicolas Joyard
Browse files
Allow multiple reps on a position, fixes
#159
parent
f18ce532
Changes
20
Expand all
Hide whitespace changes
Inline
Side-by-side
src/memopol/fixtures/data_sample.json
View file @
22643a22
This diff is collapsed.
Click to expand it.
src/memopol/templates/_position_form_modal.html
View file @
22643a22
...
...
@@ -24,7 +24,7 @@
<div
class=
"row"
>
<div
class=
"col-sm-6"
>
{% bootstrap_field position_form.representative layout='horizontal' %}
{% bootstrap_field position_form.representative
s
layout='horizontal' %}
{% bootstrap_field position_form.datetime layout='horizontal' %}
{% bootstrap_field position_form.link layout='horizontal' %}
{% bootstrap_field position_form.title layout='horizontal' %}
...
...
src/memopol/templates/blocks/_position_list.html
View file @
22643a22
...
...
@@ -38,9 +38,15 @@
<td>
{% for position in timeframe %}
<button
class=
"btn btn-default position-button"
id=
"position-button-{{ position.pk }}"
type=
"button"
data-toggle=
"modal"
data-target=
"#position-modal-{{ position.pk }}"
aria-expanded=
"false"
aria-controls=
"position-modal-{{ position.pk }}"
>
{% if show_representatives %}
<h5>
{{ position.representative }}
</h5>
{% endif %}
<h5>
{% if position.representatives.count > 1 %}
{% blocktrans with count=position.representatives.count %}
{{ count }} representatives
{% endblocktrans %}
{% else %}
{{ position.representatives.first }}
{% endif %}
</h5>
<div
class=
"text-center"
>
{{ position.datetime|naturalday }}
</div>
{% include "blocks/_themetags.html" with themes=position.themes.all exclude=theme.pk %}
...
...
@@ -59,13 +65,10 @@
</h4>
{% endif %}
<
{%
if
position.title
%}
h5
{%
else
%}
h4
{%
endif
%}
class=
"modal-title"
>
{% if show_representative %}
{% trans "Public position" %}
{% else %}
{% blocktrans with rep=position.representative %}
Public position by {{ rep }}
{% endblocktrans %}
{% endif %}
{% trans "Public position by" %}
{% for rep in position.representatives.all %}
{{ rep}}{% if not forloop.last %}, {% endif %}
{% endfor %}
</
{%
if
position.title
%}h5{%
else
%}h4{%
endif
%}
>
</div>
...
...
src/memopol/tests/base.py
View file @
22643a22
...
...
@@ -15,11 +15,10 @@ class BaseTest(ResponseDiffTestMixin, test.TestCase):
- 1 for parties
- 1 for committees
- 1 for delegations
- 2 for the position form
- 1 for representatives
- 1 for the position form
- 1 for themes
"""
left_pane_queries
=
8
left_pane_queries
=
7
def
setUp
(
self
):
RepresentativeScore
.
refresh
()
...
...
@@ -47,8 +46,7 @@ class RepresentativeBaseTest(BaseTest):
- 1 for chamber websites
- 1 for other websites
- 1 for addresses
- 1 for address country
- 1 for phone numbers related to addresses
- 2 for phone numbers related to addresses
- 1 for other phone numbers
- 2 for themes and theme scores
"""
...
...
src/memopol/tests/response_fixtures/PositionFormTest.test_select_representative.content
deleted
100644 → 0
View file @
f18ce532
<option selected="selected" value="4899">Olivier Dussopt</option>
\ No newline at end of file
src/memopol/tests/response_fixtures/PositionFormTest.test_select_representative.metadata
deleted
100644 → 0
View file @
f18ce532
{
"status_code": 200
}
\ No newline at end of file
src/memopol/tests/response_fixtures/RepresentativePositionsTest.test_position_details.content
View file @
22643a22
...
...
@@ -4,10 +4,9 @@
<button aria-label="Close" class="close" data-dismiss="modal" type="button"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">
Public position by
Public position by Olivier Dussopt
Olivier Dussopt
</h4>
</div>
...
...
@@ -69,10 +68,9 @@ Olivier Dussopt n'a pas fait de réponse aux commentaires sur son blog.</p>
<button aria-label="Close" class="close" data-dismiss="modal" type="button"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">
Public position by
Public position by Olivier Dussopt
Olivier Dussopt
</h4>
</div>
...
...
src/memopol/tests/test_position_form.py
View file @
22643a22
...
...
@@ -10,7 +10,7 @@ class PositionFormTest(BaseTest):
url
=
'/'
create_url
=
RepresentativeBaseTest
.
base_url
%
'none'
position_fixture
=
{
'position-representative'
:
1
,
'position-representative
s
'
:
1
,
'position-datetime'
:
'2016-09-01'
,
'position-link'
:
'http://example.com/test'
,
'position-kind'
:
'other'
,
...
...
@@ -20,12 +20,6 @@ class PositionFormTest(BaseTest):
'position-themes'
:
'1'
}
def
test_select_representative
(
self
):
self
.
selector_test
(
'#add-position-form #id_position-representative option[selected]'
,
RepresentativeBaseTest
.
base_url
%
'none'
)
def
test_select_theme
(
self
):
self
.
selector_test
(
'#add-position-form #id_position-themes input[checked]'
,
...
...
@@ -39,15 +33,15 @@ class PositionFormTest(BaseTest):
position
=
Position
.
objects
.
get
(
text
=
'position test text'
)
assert
position
.
datetime
==
datetime
.
date
(
2016
,
9
,
1
)
assert
position
.
representative
.
pk
==
\
self
.
position_fixture
[
'position-representative'
]
assert
position
.
representative
s
.
all
()[
0
]
.
pk
==
\
self
.
position_fixture
[
'position-representative
s
'
]
assert
position
.
link
==
self
.
position_fixture
[
'position-link'
]
assert
''
.
join
([
'%s'
%
t
.
pk
for
t
in
position
.
themes
.
all
()])
==
'1'
assert
position
.
published
is
False
def
test_create_position_without_representative
(
self
):
fixture
=
copy
.
copy
(
self
.
position_fixture
)
fixture
.
pop
(
'position-representative'
)
fixture
.
pop
(
'position-representative
s
'
)
response
=
self
.
client
.
post
(
self
.
create_url
,
fixture
)
self
.
assertResponseDiffEmpty
(
response
,
...
...
src/memopol/views/representative_detail_base.py
View file @
22643a22
...
...
@@ -67,6 +67,6 @@ class RepresentativeDetailBase(RepresentativeViewMixin, PositionFormMixin,
c
=
super
(
RepresentativeDetailBase
,
self
).
get_context_data
(
**
kwargs
)
self
.
add_representative_country_and_main_mandate
(
c
[
'object'
])
c
[
'position_form'
].
fields
[
'representative'
].
initial
=
c
[
'object'
].
pk
c
[
'position_form'
].
fields
[
'representative
s
'
].
initial
=
[
c
[
'object'
].
pk
]
return
c
src/memopol/views/representative_detail_positions.py
View file @
22643a22
...
...
@@ -24,6 +24,7 @@ class RepresentativeDetailPositions(RepresentativeDetailBase):
queryset
=
positions_qs
.
order_by
(
'-datetime'
,
'pk'
)
),
'positions__themes'
,
'positions__representatives'
,
'positions__position_score'
)
...
...
src/memopol/views/theme_detail_positions.py
View file @
22643a22
...
...
@@ -8,7 +8,7 @@ class ThemeDetailPositions(ThemeDetailBase):
def
get_queryset
(
self
):
qs
=
super
(
ThemeDetailPositions
,
self
).
get_queryset
()
qs
=
qs
.
prefetch_related
(
'positions__representative'
,
qs
=
qs
.
prefetch_related
(
'positions__representative
s
'
,
'positions__position_score'
)
return
qs
...
...
src/memopol_scores/migrations/0002_pre_multi_rep_positions.py
0 → 100644
View file @
22643a22
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'memopol_scores'
,
'0001_initial'
)
]
operations
=
[
migrations
.
RunSQL
(
"""
DROP FUNCTION refresh_scores();
"""
),
migrations
.
RunSQL
(
"""
DROP VIEW memopol_scores_v_representative_score;
"""
),
migrations
.
RunSQL
(
"""
DROP VIEW memopol_scores_v_theme_score;
"""
),
]
src/memopol_scores/migrations/0003_post_multi_rep_positions.py
0 → 100644
View file @
22643a22
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'memopol_scores'
,
'0002_pre_multi_rep_positions'
),
(
'representatives_positions'
,
'0002_multi_rep_positions'
)
]
operations
=
[
migrations
.
RunSQL
(
"""
CREATE OR REPLACE VIEW "memopol_scores_v_representative_score"
AS SELECT
"source"."representative_id" AS "representative_id" ,
SUM("source"."score") AS "score"
FROM
(
SELECT
"memopol_scores_dossierscore"."representative_id" AS "representative_id",
"memopol_scores_dossierscore"."score" AS "score"
FROM "memopol_scores_dossierscore"
UNION ALL
SELECT
"representatives_positions_position_representatives"."representative_id" AS "representative_id",
"memopol_scores_positionscore"."score" AS "score"
FROM
"memopol_scores_positionscore"
INNER JOIN "representatives_positions_position_representatives"
ON "representatives_positions_position_representatives"."position_id" = "memopol_scores_positionscore"."position_id"
) "source"
GROUP BY
"source"."representative_id"
"""
),
migrations
.
RunSQL
(
"""
CREATE OR REPLACE VIEW "memopol_scores_v_theme_score"
AS SELECT
"scoresource"."representative_id" AS "representative_id",
"scoresource"."theme_id" AS "theme_id",
SUM("scoresource"."score") AS "score"
FROM
(
-- Score contribution for proposals
SELECT
"representatives_votes_vote"."representative_id" AS "representative_id",
"proposal_themes"."theme_id" AS "theme_id",
"memopol_scores_votescore"."score" AS "score"
FROM
"memopol_scores_votescore"
INNER JOIN "representatives_votes_vote"
ON "representatives_votes_vote"."id" = "memopol_scores_votescore"."vote_id"
INNER JOIN (
-- Proposals with a theme
SELECT
"representatives_votes_proposal"."id" AS "proposal_id",
"memopol_themes_theme_proposals"."theme_id" AS "theme_id"
FROM
"representatives_votes_proposal"
INNER JOIN "memopol_themes_theme_proposals"
ON "representatives_votes_proposal"."id" = "memopol_themes_theme_proposals"."proposal_id"
UNION
-- Proposals in a dossier with a theme
SELECT
"representatives_votes_proposal"."id" AS "proposal_id",
"memopol_themes_theme_dossiers"."theme_id" AS "theme_id"
FROM
"representatives_votes_proposal"
INNER JOIN "representatives_votes_dossier"
ON "representatives_votes_dossier"."id" = "representatives_votes_proposal"."dossier_id"
INNER JOIN "memopol_themes_theme_dossiers"
ON "memopol_themes_theme_dossiers"."dossier_id" = "representatives_votes_dossier"."id"
) "proposal_themes"
ON "proposal_themes"."proposal_id" = "representatives_votes_vote"."proposal_id"
UNION ALL
-- Score contribution for positions
SELECT
"representatives_positions_position_representatives"."representative_id" AS "representative_id",
"memopol_themes_theme_positions"."theme_id" AS "theme_id",
"memopol_scores_positionscore"."score" AS "score"
FROM
"memopol_scores_positionscore"
INNER JOIN "representatives_positions_position_representatives"
ON "representatives_positions_position_representatives"."position_id" = "memopol_scores_positionscore"."position_id"
INNER JOIN "memopol_themes_theme_positions"
ON "memopol_themes_theme_positions"."position_id" = "memopol_scores_positionscore"."position_id"
) "scoresource"
GROUP BY
"scoresource"."representative_id",
"scoresource"."theme_id"
"""
),
migrations
.
RunSQL
(
"""
CREATE OR REPLACE FUNCTION refresh_scores()
RETURNS VOID AS $$
BEGIN
TRUNCATE TABLE "memopol_scores_representativescore";
TRUNCATE TABLE "memopol_scores_dossierscore";
TRUNCATE TABLE "memopol_scores_votescore";
INSERT INTO "memopol_scores_votescore" ("vote_id", "score")
SELECT "vote_id", "score" FROM "memopol_scores_v_vote_score";
INSERT INTO "memopol_scores_dossierscore" ("representative_id", "dossier_id", "score")
SELECT "representative_id", "dossier_id", "score" FROM "memopol_scores_v_dossier_score";
TRUNCATE TABLE "memopol_scores_positionscore";
INSERT INTO "memopol_scores_positionscore" ("position_id", "score")
SELECT "position_id", "score" FROM "memopol_scores_v_position_score";
TRUNCATE TABLE "memopol_scores_themescore";
INSERT INTO "memopol_scores_themescore" ("representative_id", "theme_id", "score")
SELECT
"representatives_representative"."id",
"memopol_themes_theme"."id",
COALESCE("memopol_scores_v_theme_score"."score", 0)
FROM
"representatives_representative"
INNER JOIN "memopol_themes_theme" ON 1=1
LEFT OUTER JOIN "memopol_scores_v_theme_score"
ON "memopol_scores_v_theme_score"."representative_id" = "representatives_representative"."id"
AND "memopol_scores_v_theme_score"."theme_id" = "memopol_themes_theme"."id";
INSERT INTO "memopol_scores_representativescore" ("representative_id", "score")
SELECT
"representatives_representative"."id",
COALESCE("memopol_scores_v_representative_score"."score", 0)
FROM
"representatives_representative"
LEFT OUTER JOIN "memopol_scores_v_representative_score"
ON "memopol_scores_v_representative_score"."representative_id" = "representatives_representative"."id";
END;
$$ LANGUAGE PLPGSQL;
"""
),
migrations
.
RunSQL
(
"""
SELECT refresh_scores();
"""
)
]
src/memopol_scores/tests/test_compute.py
View file @
22643a22
...
...
@@ -69,7 +69,6 @@ class ComputeTest(test.TestCase):
def
create_position
(
self
,
when
,
score
):
pos
=
Position
(
representative
=
self
.
representative
,
datetime
=
when
,
kind
=
'other'
,
title
=
'TEST'
,
...
...
@@ -79,6 +78,8 @@ class ComputeTest(test.TestCase):
published
=
True
)
pos
.
save
()
pos
.
representatives
.
add
(
self
.
representative
)
pos
.
save
()
return
pos
def
test_no_score
(
self
):
...
...
src/memopol_themes/admin.py
View file @
22643a22
from
dal
import
autocomplete
from
django
import
forms
from
django.contrib
import
admin
from
representatives_votes.admin
import
DossierAdmin
,
ProposalAdmin
from
representatives_votes.models
import
Dossier
,
Proposal
from
representatives_positions.admin
import
PositionAdmin
from
representatives_positions.admin
import
PositionAdmin
,
PositionAdminForm
from
representatives_positions.models
import
Position
from
.models
import
Theme
,
ThemeLink
...
...
@@ -73,16 +71,11 @@ class ThemedProposalAdminForm(ThemedAdminForm):
'themes'
)
class
ThemedPositionAdminForm
(
ThemedAdminForm
):
class
ThemedPositionAdminForm
(
ThemedAdminForm
,
PositionAdminForm
):
class
Meta
:
model
=
Position
fields
=
(
'representative'
,
'datetime'
,
'kind'
,
'title'
,
'score'
,
fields
=
(
'representative
s
'
,
'datetime'
,
'kind'
,
'title'
,
'score'
,
'text'
,
'link'
,
'published'
,
'themes'
)
widgets
=
{
'representative'
:
autocomplete
.
ModelSelect2
(
url
=
'representative-autocomplete'
,
)
}
class
ThemedDossierAdmin
(
DossierAdmin
):
...
...
src/representatives_positions/admin.py
View file @
22643a22
from
django
import
forms
from
django.contrib
import
admin
from
dal.autocomplete
import
ModelSelect2Multiple
from
representatives.models
import
Representative
from
.models
import
Position
...
...
@@ -17,9 +21,35 @@ def unpublish_positions(modeladmin, request, queryset):
unpublish_positions
.
short_description
=
'Unpublish selected positions'
class
PositionAdminForm
(
forms
.
ModelForm
):
representatives
=
forms
.
ModelMultipleChoiceField
(
queryset
=
Representative
.
objects
.
all
(),
required
=
False
,
widget
=
ModelSelect2Multiple
(
url
=
'representative-autocomplete'
,
)
)
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
PositionAdminForm
,
self
).
__init__
(
*
args
,
**
kwargs
)
if
self
.
instance
and
self
.
instance
.
pk
:
self
.
fields
[
'representatives'
].
initial
=
\
self
.
instance
.
representatives
.
all
()
def
save
(
self
,
commit
=
True
):
item
=
super
(
PositionAdminForm
,
self
).
save
(
commit
=
False
)
item
.
save
()
item
.
representatives
=
self
.
cleaned_data
[
'representatives'
]
if
commit
:
self
.
save_m2m
()
return
item
class
PositionAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'representative'
,
'kind'
,
'short_title'
,
'short_text'
,
...
...
@@ -32,4 +62,7 @@ class PositionAdmin(admin.ModelAdmin):
list_filter
=
(
'published'
,)
actions
=
(
publish_positions
,
unpublish_positions
)
form
=
PositionAdminForm
admin
.
site
.
register
(
Position
,
PositionAdmin
)
src/representatives_positions/forms.py
View file @
22643a22
from
django
import
forms
from
datetimewidget.widgets
import
DateWidget
from
dal.autocomplete
import
ModelSelect2Multiple
from
memopol_themes.models
import
Theme
from
.models
import
Position
...
...
@@ -15,13 +16,16 @@ class PositionForm(forms.ModelForm):
class
Meta
:
model
=
Position
fields
=
[
'representative'
,
'link'
,
'datetime'
,
'themes'
,
'title'
,
fields
=
[
'representative
s
'
,
'link'
,
'datetime'
,
'themes'
,
'title'
,
'kind'
,
'text'
]
widgets
=
{
'datetime'
:
DateWidget
(
usel10n
=
True
,
bootstrap_version
=
3
),
'representatives'
:
ModelSelect2Multiple
(
url
=
'representative-autocomplete'
,
)
}
...
...
src/representatives_positions/migrations/0002_multi_rep_positions.py
0 → 100644
View file @
22643a22
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
def
migrate_position_representatives
(
apps
,
schema_editor
):
Position
=
apps
.
get_model
(
"representatives_positions"
,
"Position"
)
for
pos
in
Position
.
objects
.
all
():
pos
.
representatives
=
[
pos
.
representative
]
pos
.
save
()
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'memopol_scores'
,
'0002_pre_multi_rep_positions'
),
(
'representatives_positions'
,
'0001_initial'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'position'
,
name
=
'representatives'
,
field
=
models
.
ManyToManyField
(
to
=
'representatives.Representative'
),
),
migrations
.
RunPython
(
migrate_position_representatives
),
migrations
.
RemoveField
(
model_name
=
'position'
,
name
=
'representative'
,
),
migrations
.
AlterField
(
model_name
=
'position'
,
name
=
'representatives'
,
field
=
models
.
ManyToManyField
(
related_name
=
'positions'
,
to
=
'representatives.Representative'
),
),
]
src/representatives_positions/models.py
View file @
22643a22
...
...
@@ -9,12 +9,13 @@ KIND_CHOICES = (
(
'social'
,
'Social network'
),
(
'press'
,
'Press interview'
),
(
'parliament'
,
'Parliament debate'
),
(
'amendment'
,
'Amendment'
),
)
class
Position
(
models
.
Model
):
representative
=
models
.
ForeignKey
(
Representative
,
related_name
=
'positions'
)
representative
s
=
models
.
ManyToManyField
(
Representative
,
related_name
=
'positions'
)
datetime
=
models
.
DateField
()
kind
=
models
.
CharField
(
max_length
=
64
,
choices
=
KIND_CHOICES
,
default
=
'other'
)
...
...
src/representatives_positions/views.py
View file @
22643a22
...
...
@@ -16,7 +16,7 @@ class PositionFormMixin(generic.View):
position_created
=
False
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
if
'position-representative'
in
request
.
POST
:
if
'position-representative
s
'
in
request
.
POST
:
self
.
position_form
=
PositionForm
(
request
.
POST
,
prefix
=
'position'
)
if
self
.
position_form
.
is_valid
():
self
.
position_form
.
save
()
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment