Compare commits

...

7 Commits

56 changed files with 1256 additions and 957 deletions

8
.env Normal file
View File

@ -0,0 +1,8 @@
FLASK_APP=server.py
FLASK_ENV=development
BABEL_DEFAULT_LOCALE=fr
BCRYPT_ROUNDS=6
SECRET_KEY=blabla
SQLALCHEMY_DATABASE_URI=mysql://politikorama:politikorama@localhost/politikorama
API_PER_PAGE=30

View File

@ -12,4 +12,4 @@ api = Api()
babel = Babel()
db = SQLAlchemy()
login_manager = LoginManager()
migrate = Migrate()
migrate = Migrate(compare_type=True)

View File

@ -1,22 +1,14 @@
# encoding: utf-8
from app.controller.admin.importer import (ImportAddressView, ImportContactView,
ImportCountryView, ImportDecisionView, ImportEntityView, ImportMatterView,
ImportMembershipView, ImportRecommendationView, ImportRepresentativeView,
ImportRoleView, ImportStanceView, ImportTypeView)
from app.controller.admin.country import ImportCountryView
from app.controller.admin.entity import ImportEntityView
from app.controller.admin.membership import ImportMembershipView
from app.controller.admin.representative import ImportRepresentativeView
admin_routes = (
(ImportAddressView, "Addresses", "addresses", "Import"),
(ImportContactView, "Contacts", "contacts", "Import"),
(ImportCountryView, "Countries", "countries", "Import"),
(ImportDecisionView, "Decisions", "decisions", "Import"),
(ImportEntityView, "Entities", "entities", "Import"),
(ImportMatterView, "Matters", "matters", "Import"),
(ImportMembershipView, "Memberships", "memberships", "Import"),
(ImportRecommendationView, "Recommendations", "recommendations", "Import"),
(ImportRepresentativeView, "Representatives", "representatives", "Import"),
(ImportRoleView, "Roles", "roles", "Import"),
(ImportStanceView, "Stances", "stances", "Import"),
(ImportTypeView, "Types", "types", "Import"),
)

View File

@ -0,0 +1,76 @@
# encoding: utf-8
from flask import current_app, g, request, redirect, url_for
from flask_admin import BaseView, expose
from slugify import slugify
from app.form.admin import ImportForm
from app.controller.admin.importer import default_import
from app.model.country import CountryModel
class ImportCountryView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
g.what = {
"title": "Import countries",
"description": "Importing countries will add unknown ones and update"
" known ones. If a country is not present in imported file it"
" will not be deleted.",
"endpoint": "countries.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line represents column headers.",
"Other lines are values:",
"One column MUST be 'name'",
"One column COULD be 'alpha_2'",
"One column COULD be 'alpha_3'",
"One column COULD be 'official_name'",
],
"examples": [
"name,alpha_2",
"Spain,es",
"Germany,DE",
],
}
headers = {
"name": None,
"alpha_2": "optional",
"alpha_3": "optional",
"official_name": "optional",
}
reader = default_import(headers)
if len(g.errors) == 0 and reader is not None:
for row in reader:
country = CountryModel.query.filter_by(
name=row[headers["name"]],
).first()
if headers["alpha_2"] is not None:
alpha_2 = row[headers["alpha_2"]]
else:
alpha_2 = None
if headers["alpha_3"] is not None:
alpha_3 = row[headers["alpha_3"]]
else:
alpha_3 = None
if headers["official_name"] is not None:
official_name = row[headers["official_name"]]
else:
official_name = None
if country is None:
country = CountryModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
alpha_2 = alpha_2,
alpha_3 = alpha_3,
official_state_name = official_name,
)
g.messages.append(
f"{row[headers['name']]} added."
)
country.save()
else:
# Mise à jour du pays !
pass
return self.render("admin/import.html")

View File

@ -0,0 +1,118 @@
# encoding: utf-8
from datetime import datetime
from flask import current_app, g, request, redirect, url_for
from flask_admin import BaseView, expose
from slugify import slugify
from app.form.admin import ImportForm
from app.controller.admin.importer import default_import
from app.model.entity import EntityModel
from app.model.type import TypeModel
class ImportEntityView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
g.what = {
"title": "Import entities",
"description": "Importing entities will add unknown ones and update"
" known ones. If an entity is not present in imported file it"
" will not be deleted.",
"endpoint": "entities.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line represents column headers.",
"Other lines are values:",
"One column MUST be 'name'",
"One column MUST be 'reference'",
"One column MUST be 'parent_reference'",
"One column MUST be 'start'",
"One column COULD be 'end'",
"One column COULD be 'source'",
"One column COULD be 'type_code'",
"One column COULD be 'type_name'",
],
"examples": [
"name,reference,parent_reference,source,type_code,type_name,start,end",
"Thingy Commission,CO1234,NA123,French National Assembly,COMM,Commission,2020-01-01,",
"Group Assembly,NA123,,French National Assembly,GROU,Group,2019-01-01,2099-12-31",
],
}
headers = {
"name": None,
"reference": None,
"parent_reference": None,
"source": "optional",
"type_code": "optional",
"type_name": "optional",
"start": None,
"end": "optional",
}
reader = default_import(headers)
if len(g.errors) == 0 and reader is not None:
for row in reader:
if headers["type_code"] is not None:
entity_type = TypeModel.query.filter_by(
code=row[headers["type_code"]].upper(),
).first()
if entity_type is None:
entity_type = TypeModel(
code=row[headers["type_code"]].upper(),
name=row[headers["type_name"]],
slug=slugify(row[headers["type_name"]]),
)
entity_type.save()
if headers["source"] is not None:
source = row[headers["source"]]
else:
source = None
if row[headers["start"]] != "":
start = datetime.strptime(
row[headers["start"]], "%Y-%m-%d"
).date()
else:
start = None
end = None
if headers["end"] is not None:
if row[headers["end"]] != "":
end = datetime.strptime(
row[headers["end"]], "%Y-%m-%d"
).date()
parent = EntityModel.query.filter_by(
reference=row[headers["parent_reference"]].upper(),
).first()
if parent is not None:
parent_id = parent.id
else:
parent_id = None
entity = EntityModel.query.filter_by(
reference=row[headers["reference"]].upper()
).first()
if entity is None:
entity = EntityModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
reference = row[headers["reference"]].upper(),
parent_id = parent_id,
source = source,
type = entity_type,
start = start,
end = end,
active = True if end is None else False,
)
g.messages.append(
f"{row[headers['name']]} added."
)
entity.save()
else:
# updating information ?
pass
return self.render("admin/import.html")

View File

@ -11,18 +11,7 @@ from slugify import slugify
from sqlalchemy import or_
from app.form.admin import ImportForm
from app.model.address import AddressModel
from app.model.contact import ContactModel
from app.model.country import CountryModel
from app.model.decision import DecisionModel
from app.model.entity import EntityModel
from app.model.matter import MatterModel
from app.model.membership import MembershipModel
from app.model.recommendation import RecommendationModel
from app.model.representative import RepresentativeModel
from app.model.role import RoleModel
from app.model.stance import StanceModel
from app.model.type import TypeModel
from app import model
def default_import(headers):
@ -91,14 +80,14 @@ class ImportAddressView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
address_country = CountryModel.query.filter_by(
address_country = model.CountryModel.query.filter_by(
code=row[headers["country_code"]].upper()
).first()
address = AddressModel.query.filter_by(
address = model.AddressModel.query.filter_by(
slug==slugify(row[headers["name"]]),
).first()
if address is None:
address = AddressModel(
address = model.AddressModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
country = address_country,
@ -195,21 +184,21 @@ class ImportContactView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
contact_representative = RepresentativeModel.query.filter_by(
contact_representative = model.RepresentativeModel.query.filter_by(
slug=row[headers["representative_slug"]],
).first()
if headers["addres_slug"] is not None:
contact_address = AddressModel.query.filter_by(
contact_address = model.AddressModel.query.filter_by(
slug==row[headers["address_slug"]],
).first()
else:
contact_address = None
contact = ContactModel.query.filter_by(
contact = model.ContactModel.query.filter_by(
slug==slugify(row[headers["name"]]),
representative_id=contact_representative.id,
).first()
if contact is None:
contact = ContactModel(
contact = model.ContactModel(
representative = contact_representative,
address = contact_address,
name = row[headers["name"]],
@ -233,52 +222,6 @@ class ImportContactView(BaseView):
return self.render("admin/import.html")
class ImportCountryView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
headers = {"name": None, "code": None}
reader = default_import(headers)
g.what = {
"title": "Import countries",
"description": "Importing countries will add unknown ones and update known"
" ones. If a country is not present in imported file it will not be"
" deleted.",
"endpoint": "countries.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line chould be column headers.",
"Other lines are values.",
"One column MUST be 'name'.",
"One column MUST be 'code'.",
],
"examples": [
"name,code",
"France,FR",
"United States of America,US",
"Spain,SP",
],
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
country = CountryModel.query.filter_by(code=row[headers["code"]]).first()
if country is None:
country = CountryModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
code = row[headers["code"]].upper(),
)
g.messages.append(f"{row[headers['name']]} added.")
country.save()
else:
if country.name != row[headers["name"]]:
country.name = row[headers["name"]]
country.slug = slugify(row[headers["name"]])
g.messages.append(f"{row[headers['name']]} updated.")
country.save()
return self.render("admin/import.html")
class ImportDecisionView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
@ -311,18 +254,18 @@ class ImportDecisionView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
decision_representative = RepresentativeModel.query.filter_by(
decision_representative = model.RepresentativeModel.query.filter_by(
slug=row[headers["representative_slug"]],
).first()
decision_recommendation = RecommendationModel.query.filter_by(
decision_recommendation = model.RecommendationModel.query.filter_by(
slug=row[headers["recommendation_slug"]],
).first()
decision = DecisionModel.query.filter_by(
decision = model.DecisionModel.query.filter_by(
representative=decision_representative,
recommendation=decision_recommendation,
).first()
if decision is None:
decision = DecisionModel(
decision = model.DecisionModel(
representative = decision_representative,
recommendation = decision_recommendation,
value = value,
@ -378,13 +321,13 @@ class ImportEntityView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
entity_type = TypeModel.query.filter_by(
entity_type = model.TypeModel.query.filter_by(
code=row[headers["type_code"]]
).first()
entity_country = CountryModel.query.filter_by(
entity_country = model.CountryModel.query.filter_by(
code=row[headers["country_code"]].upper()
).first()
entity = EntityModel.query.filter_by(
entity = model.EntityModel.query.filter_by(
code=row[headers["code"]]
).first()
if row[headers["start"]] != "":
@ -400,7 +343,7 @@ class ImportEntityView(BaseView):
else:
picture = None
if entity is None:
entity = EntityModel(
entity = model.EntityModel(
type = entity_type,
country = entity_country,
name = row[headers["name"]],
@ -470,7 +413,7 @@ class ImportMatterView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
matter = MatterModel.query.filter_by(
matter = model.MatterModel.query.filter_by(
slug=slugify(row[headers["name"]]),
).first()
if headers["description"] is not None:
@ -478,7 +421,7 @@ class ImportMatterView(BaseView):
else:
description = None
if matter is None:
matter = DecisionModel(
matter = model.DecisionModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
description = description,
@ -530,13 +473,13 @@ class ImportMembershipView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
representative = RepresentativeModel.query.filter_by(
representative = model.RepresentativeModel.query.filter_by(
slug=slugify(row[headers["representative_slug"]]),
).first()
entity = EntityModel.query.filter_by(
entity = model.EntityModel.query.filter_by(
code=row[headers["entity_code"]],
).first()
role = RoleModel.query.filter_by(
role = model.RoleModel.query.filter_by(
code=row[headers["role_code"]],
).first()
if row[headers["start"]] != "":
@ -547,14 +490,14 @@ class ImportMembershipView(BaseView):
end_date = datetime.strptime(row[headers["end"]], "%Y-%m-%d")
else:
end_date = None
membership = MembershipModel.query.filter_by(
membership = model.MembershipModel.query.filter_by(
representative=representative,
entity=entity,
role=role,
start=start_date,
).first()
if membership is None:
membership = MembershipModel(
membership = model.MembershipModel(
representative = representative,
entity = entity,
role = role,
@ -614,10 +557,10 @@ class ImportRecommendationView(BaseView):
}
if len(g.errors) == 0 and reader is not None:
for row in reader:
matter = MatterModel.query.filter_by(
matter = model.MatterModel.query.filter_by(
slug=row[headers["matter_slug"]],
).first()
entity = EntityModel.query.filter_by(
entity = model.EntityModel.query.filter_by(
code=row[headers["entity_code"]],
).first()
if row[headers["date"]] != "":
@ -632,13 +575,13 @@ class ImportRecommendationView(BaseView):
weight = int(row[headers["weight"]])
else:
weight = 1
recommendation = RecommendationModel.query.filter_by(
recommendation = model.RecommendationModel.query.filter_by(
matter=matter,
entity=entity,
code=row[headers["code"]],
).first()
if recommendation is None:
recommendation = RecommendationModel(
recommendation = model.RecommendationModel(
matter = matter,
entity = entity,
name = row[headers["name"]],
@ -676,102 +619,6 @@ class ImportRecommendationView(BaseView):
return self.render("admin/import.html")
class ImportRepresentativeView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
headers = {
"code": None,
"name": None,
"picture": None,
"nationality": None,
"sex": None,
"birth_date": None,
"birth_place": None,
"job": None,
}
reader = default_import(headers)
g.what = {
"title": "Import representatives",
"description": "Importing representatives will add unknown ones and update"
" known ones. If a representative is not present in imported file it"
" will not be deleted.",
"endpoint": "representatives.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line chould be column headers.",
"Other lines are values.",
"One column MUST be 'code' and be the unique identifier of the representative.",
"One column MUST be 'name'.",
"One column MUST be 'picture'.",
"One column MUST be 'nationality' and be an ISO country code.",
"One column MUST be 'sex' and be 'F' or 'M'.",
"One column MUST be 'birth_date'.",
"One column MUST be 'birth_place'.",
"One column MUST be 'birth_job'.",
],
"examples": [
"name,iso2,iso3",
"France,FR,FRA",
"United States of America,US,USA",
"Spain,SP,SPA",
],
}
if len(g.errors) == 0 and reader is not None:
# Values
for row in reader:
representative = RepresentativeModel.query.filter_by(
code=row[headers["code"]],
).first()
country = CountryModel.query.filter_by(
code=row[headers["nationality"]].upper(),
).first()
if row[headers["birth_date"]] != "":
birth_date = datetime.strptime(row[headers["birth_date"]], "%Y-%m-%d").date()
else:
birth_date = None
if representative is None:
representative = RepresentativeModel(
code = row[headers["code"]],
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
picture = row[headers["picture"]],
nationality = country,
sex = row[headers["sex"]],
birth_date = birth_date,
birth_place = row[headers["birth_place"]],
job = row[headers["job"]],
)
g.messages.append(f"{row[headers['name']]} added.")
representative.save()
else:
updated = False
if representative.picture != row[headers["picture"]]:
representative.picture = row[headers["picture"]]
updated = True
if representative.nationality != country:
representative.nationality = country
updated = True
if representative.sex != row[headers["sex"]]:
representative.sex = row[headers["sex"]]
updated = True
if representative.birth_date != birth_date:
print(representative.birth_date)
print(birth_date)
representative.birth_date = birth_date
updated = True
if representative.birth_place != row[headers["birth_place"]]:
representative.birth_place = row[headers["birth_place"]]
updated = True
if representative.job != row[headers["job"]]:
representative.job = row[headers["job"]]
updated = True
if updated:
g.messages.append(f"{row[headers['name']]} updated.")
representative.save()
return self.render("admin/import.html")
class ImportRoleView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
@ -800,9 +647,9 @@ class ImportRoleView(BaseView):
if len(g.errors) == 0 and reader is not None:
# Values
for row in reader:
role = RoleModel.query.filter_by(code=row[headers["code"]]).first()
role = model.RoleModel.query.filter_by(code=row[headers["code"]]).first()
if role is None:
role = RoleModel(
role = model.RoleModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
code = row[headers["code"]],
@ -856,21 +703,21 @@ class ImportStanceView(BaseView):
# Values
for row in reader:
# Representative
representative = RepresentativeModel.query.filter_by(
representative = model.RepresentativeModel.query.filter_by(
slug=row[headers["slug"]],
).first()
if representative is None:
representative = RepresentativeModel(
representative = model.RepresentativeModel(
name = row[headers["name"]],
slug = row[headers["slug"]],
)
representative.save()
# Matter
matter = MatterModel.query.filter_by(
matter = model.MatterModel.query.filter_by(
slug=slugify(row[headers["matter"]]),
).first()
if matter is None:
matter = MatterModel(
matter = model.MatterModel(
name = row[headers["matter"]],
slug = slugify(row[headers["matter"]]),
)
@ -880,7 +727,7 @@ class ImportStanceView(BaseView):
else:
stance_date = None
# Stance
stance = StanceModel.query.filter_by(
stance = model.StanceModel.query.filter_by(
representative=representative,
matter=matter,
subject=row[headers["subject"]],
@ -888,7 +735,7 @@ class ImportStanceView(BaseView):
source_url=row[headers["source_url"]],
).first()
if stance is None:
stance = StanceModel(
stance = model.StanceModel(
representative = representative,
matter = matter,
subject = row[headers["subject"]],
@ -947,9 +794,9 @@ class ImportTypeView(BaseView):
if len(g.errors) == 0 and reader is not None:
# Values
for row in reader:
type_ = TypeModel.query.filter_by(code=row[headers["code"]]).first()
type_ = model.TypeModel.query.filter_by(code=row[headers["code"]]).first()
if type_ is None:
type_ = TypeModel(
type_ = model.TypeModel(
name = row[headers["name"]],
slug = slugify(row[headers["name"]]),
code = row[headers["code"]],

View File

@ -0,0 +1,119 @@
# encoding: utf-8
from datetime import datetime
from flask import current_app, g, request, redirect, url_for
from flask_admin import BaseView, expose
from slugify import slugify
from sqlalchemy import or_
from app.form.admin import ImportForm
from app.controller.admin.importer import default_import
from app.model.entity import EntityModel
from app.model.membership import MembershipModel
from app.model.representative_origin import RepresentativeOriginModel
from app.model.role import RoleModel
class ImportMembershipView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
g.what = {
"title": "Import memberships",
"description": "Importing memberships will add unknown ones and update"
" known ones. If a membership is not present in imported file it"
" will not be deleted.",
"endpoint": "memberships.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line represents column headers.",
"Other lines are values:",
"One column MUST be 'reference'",
"One column MUST be 'representative_reference'",
"One column MUST be 'entity_reference'",
"One column MUST be 'role_code'",
"One column MUST be 'role'",
"One column MUST be 'start'",
"One column COULD be 'end'",
],
"examples": [
"reference,representative_reference,entity_reference,role_code,role,start,end",
"12,23,34,MBR,Member,2020-01-01,",
"45,67,89,PDT,President,2018-01-01,2019-12-31",
],
}
headers = {
"reference": None,
"representative_reference": None,
"entity_reference": None,
"role_code": None,
"role": None,
"start": None,
"end": "optional",
}
reader = default_import(headers)
if len(g.errors) == 0 and reader is not None:
for row in reader:
role = RoleModel.query.filter_by(
code=row[headers["role_code"]].upper()
).first()
if role is None:
role = RoleModel(
code=row[headers["role_code"]].upper(),
name=row[headers["role"]],
slug=slugify(row[headers["role"]]),
)
role.save()
entity = EntityModel.query.filter_by(
reference=row[headers["entity_reference"]].upper()
).first()
if entity is None:
g.messages.append("UNKNOWN entity :"
f"{row[headers['entity_reference']].upper()}"
)
continue
representative_origin = RepresentativeOriginModel.query.filter_by(
reference=row[headers["representative_reference"]].upper(),
).first()
if representative_origin is None:
g.messages.append("UNKNOWN representative :"
f"{row[headers['representative_reference']].upper()}"
)
continue
if row[headers["start"]] != "":
start = datetime.strptime(
row[headers["start"]], "%Y-%m-%d"
).date()
else:
start = None
end = None
if headers["end"] is not None:
if row[headers["end"]] != "":
end = datetime.strptime(
row[headers["end"]], "%Y-%m-%d"
).date()
membership = MembershipModel.query.filter_by(
reference=row[headers["reference"]].upper()
).first()
if membership is None:
membership = MembershipModel(
representative_id = representative_origin.representative_id,
entity_id = entity.id,
reference = row[headers["reference"]].upper(),
role_id = role.id,
start = start,
end = end,
active = True if end is None else False,
)
g.messages.append(
f"{role.name} added"
f" for {representative_origin.representative.first_name}"
f" {representative_origin.representative.last_name}."
)
membership.save()
return self.render("admin/import.html")

View File

@ -0,0 +1,156 @@
# encoding: utf-8
from datetime import datetime
from flask import current_app, g, request, redirect, url_for
from flask_admin import BaseView, expose
from slugify import slugify
from sqlalchemy import or_
from app.form.admin import ImportForm
from app.controller.admin.importer import default_import
from app.model.country import CountryModel
from app.model.representative import RepresentativeModel
from app.model.source import SourceModel
from app.model.representative_origin import RepresentativeOriginModel
class ImportRepresentativeView(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
g.what = {
"title": "Import representatives",
"description": "Importing representatives will add unknown ones and update"
" known ones. If a representative is not present in imported file it"
" will not be deleted.",
"endpoint": "representatives.index",
"formats": [
"File format accepted is CSV (Comma Separated Values).",
"First line represents column headers.",
"Other lines are values:",
"One column MUST be 'source'",
"One column MUST be 'reference'",
"One column MUST be 'first_name'",
"One column MUST be 'last_name'",
"One column COULD be 'picture' and be a URI",
"One column COULD be 'birth_date'",
"One column COULD be 'birth_place'",
"One column COULD be 'birth_region'",
"One column COULD be 'birth_country'",
"One column COULD be 'nationality'",
"One column COULD be 'job'",
],
"examples": [
"source,reference,first_name,last_name,job",
"somewhere,123,Victor,Hugo,Writer",
"somewhere,456,Antonio,Vivaldi,Musician",
],
}
headers = {
"source": None,
"reference": None,
"first_name": None,
"last_name": None,
"picture": "optional",
"birth_date": "optional",
"birth_place": "optional",
"birth_region": "optional",
"birth_country": "optional",
"nationality": "optional",
"job": "optional",
}
reader = default_import(headers)
if len(g.errors) == 0 and reader is not None:
for row in reader:
if row[headers["nationality"]] !="":
nationality = CountryModel.query.filter(or_(
CountryModel.slug == slugify(row[headers["nationality"]]),
CountryModel.alpha_2 == row[headers["nationality"]].upper(),
)).first()
if nationality is None:
if len(row[headers["nationality"]])==2:
nationality = CountryModel(
name=row[headers["nationality"]],
slug=slugify(row[headers["nationality"]]),
alpha_2=row[headers["nationality"]].upper(),
)
else:
nationality = CountryModel(
name=row[headers["nationality"]],
slug=slugify(row[headers["nationality"]]),
)
nationality.save()
if row[headers["birth_country"]] !="":
birth_country = CountryModel.query.filter(or_(
CountryModel.slug == slugify(row[headers["birth_country"]]),
CountryModel.alpha_2 == row[headers["birth_country"]].upper(),
)).first()
if birth_country is None:
if len(row[headers["birth_country"]])==2:
birth_country = CountryModel(
name=row[headers["birth_country"]],
slug=slugify(row[headers["birth_country"]]),
alpha_2=row[headers["birth_country"]].upper(),
)
else:
birth_country = CountryModel(
name=row[headers["birth_country"]],
slug=slugify(row[headers["birth_country"]]),
)
birth_country.save()
if row[headers["birth_date"]] != "":
birth_date = datetime.strptime(
row[headers["birth_date"]], "%Y-%m-%d"
).date()
else:
birth_date = None
source = SourceModel.query.filter_by(
slug=slugify(row[headers["source"]]),
).first()
if source is None:
source = SourceModel(
name = row[headers["source"]],
slug = slugify(row[headers["source"]]),
)
source.save()
representative = RepresentativeModel.query.filter_by(
last_name=row[headers["last_name"]],
).filter_by(
first_name=row[headers["first_name"]],
).first()
if representative is None:
representative = RepresentativeModel(
first_name = row[headers["first_name"]],
last_name = row[headers["last_name"]],
birth_date = birth_date,
birth_place = row[headers["birth_place"]],
birth_region = row[headers["birth_region"]],
birth_country = birth_country,
job = row[headers["job"]],
nationality = nationality,
picture = row[headers["picture"]],
)
g.messages.append(
f"{row[headers['first_name']]} {row[headers['last_name']]}"
" added."
)
representative.save()
representative_origin = RepresentativeOriginModel.query.filter_by(
source_id=source.id,
).filter_by(
reference=row[headers["reference"]].upper(),
).first()
if representative_origin is None:
representative_origin = RepresentativeOriginModel(
source_id = source.id,
representative_id = representative.id,
reference = row[headers["reference"]].upper(),
)
representative_origin.save()
return self.render("admin/import.html")

View File

@ -1,45 +0,0 @@
# encoding: utf-8
from flask import request, current_app
from flask_restful import Resource
from sqlalchemy import or_
from app.model.country import CountryModel
class CountriesApi(Resource):
def get(self):
page = int(request.args.get("page", 1))
query = CountryModel.query
if request.args.get("name", "") != "":
query = query.filter(
CountryModel
.name
.like('%%%s%%' % request.args.get("name", ""))
)
if request.args.get("iso2", "") != "":
query = query.filter_by(iso2=request.args.get("iso2", "").upper())
if request.args.get("iso3", "") != "":
query = query.filter_by(iso3=request.args.get("iso3", "").upper())
if request.args.get("m49", "") != "":
query = query.filter_by(m49=request.args.get("m49", "").upper())
query = query.order_by(CountryModel.name)
return [
country.serialize()
for country
in query
.paginate(page, current_app.config['API_PER_PAGE'], error_out=False)
.items
]
class CountryApi(Resource):
def get(self, country_id):
country = CountryModel.query.filter(or_(
CountryModel.iso2==country_id,
CountryModel.iso3==country_id,
CountryModel.m49==country_id,
)).first()
if country is None:
return None, 404
return country.serialize()

View File

@ -10,20 +10,61 @@ from app.model.representative import RepresentativeModel
class RepresentativesApi(Resource):
def get(self):
page = int(request.args.get("page", 1))
print(page)
query = RepresentativeModel.query
if request.args.get("name", "") != "":
query = query.filter(
RepresentativeModel
.name
.like('%%%s%%' % request.args.get("name", ""))
)
query = query.order_by(RepresentativeModel.name)
query = query.filter(or_(
RepresentativeModel.last_name.like(
f"%{request.args.get('name')}%"
),
RepresentativeModel.first_name.like(
f"%{request.args.get('name')}%"
),
))
query = query.order_by(RepresentativeModel.last_name)
return [
representative.serialize()
{
"first_name": representative.first_name,
"last_name": representative.last_name,
"full_name": representative.full_name,
"picture": representative.picture,
"active": True,
"memberships": [
{
"reference": membership.reference,
"source": membership.entity.source,
"start_date": membership.start.strftime("%Y-%m-%d"),
"end_date": membership.end.strftime("%Y-%m-%d") if membership.end is not None else None,
"entity": {
"reference": membership.entity.reference,
"type": {
"code": membership.entity.type.code,
"name": membership.entity.type.name,
},
"name": membership.entity.name,
},
"role": {
"code": membership.role.code,
"name": membership.role.name,
},
}
for membership
in representative.memberships
],
"references": [
{
"reference": reference.reference,
"source": reference.source.name,
}
for reference
in representative.references
],
}
for representative
in query
.paginate(page, current_app.config['API_PER_PAGE'], error_out=False)
.items
in query.paginate(
page, current_app.config["API_PER_PAGE"], error_out=False
).items
]
@ -32,4 +73,5 @@ class RepresentativeApi(Resource):
representative = RepresentativeModel.query.get(representative_id)
if representative is None:
return None, 404
print(representative.serialize())
return representative.serialize()

View File

@ -5,41 +5,16 @@ import random
from flask import g, render_template
from app import model
from app.controller.controller import Controller
from app.model.decision import DecisionModel
from app.model.matter import MatterModel
from app.model.recommendation import RecommendationModel
from app.model.representative import RepresentativeModel
from app.model.stance import StanceModel
from sqlalchemy import desc
from sqlalchemy.sql.expression import func
class Core(Controller):
def home(self):
random.seed(int(datetime.today().timestamp()/100))
try:
g.motd = random.choice(MatterModel.query.filter_by(
active=True
).all())
except:
g.motd = None
g.rotd = random.choice(RepresentativeModel.query.filter_by(
active=True
).all())
g.last_decisions = DecisionModel.query.join(DecisionModel.recommendation).order_by(RecommendationModel.date.desc()).limit(10).all()
g.last_stances = StanceModel.query.order_by(StanceModel.date.desc()).limit(10).all()
return render_template("core/home.html")
def representative(self, representative_id=None):
if representative_id is None:
representative_id = random.choice(RepresentativeModel.query.filter_by(
active=True
).all()).id
g.representative = RepresentativeModel.query.get(representative_id)
g.title = g.representative.name
return render_template("core/representative.html")
def about(self):
return render_template("core/about.html")

View File

@ -0,0 +1,23 @@
# encoding: utf-8
from datetime import datetime
import random
from flask import g, redirect, render_template, url_for
from app import model
from app.controller.controller import Controller
class Representative(Controller):
def view(self, representative_id=None):
if representative_id is None:
g.representative = random.choice(
model.RepresentativeModel.query.filter_by(active=True).all()
)
else:
g.representative = model.RepresentativeModel.query.get(representative_id)
if g.representative is None:
return redirect(url_for("core.home"))
g.title = g.representative.name
return render_template("representative/view.html")

View File

@ -3,16 +3,12 @@
This module imports models to allow alembic and flask to find them.
"""
from app.model.user import UserModel
from app.model.country import CountryModel
from app.model.representative import RepresentativeModel
from app.model.address import AddressModel
from app.model.contact import ContactModel
from app.model.type import TypeModel
from app.model.entity import EntityModel
from app.model.representative import RepresentativeModel
from app.model.role import RoleModel
from app.model.source import SourceModel
from app.model.representative_origin import RepresentativeOriginModel
from app.model.membership import MembershipModel
from app.model.matter import MatterModel
from app.model.recommendation import RecommendationModel
from app.model.decision import DecisionModel
from app.model.stance import StanceModel

View File

@ -1,32 +0,0 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
class AddressModel(db.Model, Model):
__tablename__ = "address"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
country_id = db.Column(db.Integer, db.ForeignKey("country.id"))
country = db.relationship("CountryModel")
number = db.Column(db.String(2000))
street = db.Column(db.String(2000))
miscellaneous = db.Column(db.String(2000))
city = db.Column(db.String(2000))
zipcode = db.Column(db.String(2000))
building = db.Column(db.String(2000))
floor = db.Column(db.String(2000))
stair = db.Column(db.String(2000))
office = db.Column(db.String(2000))
latitude = db.Column(db.String(2000))
longitude = db.Column(db.String(2000))
class AdminView(View):
column_default_sort = [("name", False), ("country.name", True)]
column_filters = ["name", "country.name"]
admin.add_view(AdminView(AddressModel, db.session, name="Address", category="CRUD"))

View File

@ -1,34 +0,0 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
class ContactModel(db.Model, Model):
__tablename__ = "contact"
id = db.Column(db.Integer, primary_key=True)
representative_id = db.Column(db.Integer, db.ForeignKey("representative.id"))
representative = db.relationship(
"RepresentativeModel", backref=db.backref("contacts", lazy="dynamic")
)
address_id = db.Column(db.Integer, db.ForeignKey("address.id"))
address = db.relationship(
"AddressModel", backref=db.backref("contacts", lazy="dynamic")
)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
value = db.Column(db.String(2000))
def __repr__(self):
return self.name
class AdminView(View):
column_default_sort = "name"
column_filters = ["representative.name", "name"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
admin.add_view(AdminView(ContactModel, db.session, name="Contact", category="CRUD"))

View File

@ -7,27 +7,14 @@ from app.model.model import Model, View
class CountryModel(db.Model, Model):
__tablename__ = "country"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
code = db.Column(db.String(2000))
@property
def flag(self):
"""
HTML unicode sequence for display country flag.
"""
return "".join([f"&#{hex(127397+ord(l))[1:]};" for l in self.code])
name = db.Column(db.String(200))
slug = db.Column(db.String(200))
official_state_name = db.Column(db.String(200))
alpha_2 = db.Column(db.String(2))
alpha_3 = db.Column(db.String(3))
def __repr__(self):
return self.name
class AdminView(View):
column_default_sort = "name"
column_filters = ["name", "code"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
admin.add_view(AdminView(CountryModel, db.session, name="Country", category="CRUD"))
admin.add_view(View(CountryModel, db.session, name="Country", category="CRUD"))

View File

@ -1,29 +0,0 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
class DecisionModel(db.Model, Model):
__tablename__ = "decision"
id = db.Column(db.Integer, primary_key=True)
representative_id = db.Column(db.Integer, db.ForeignKey("representative.id"))
representative = db.relationship(
"RepresentativeModel", backref=db.backref("decisions", lazy="dynamic")
)
recommendation_id = db.Column(db.Integer, db.ForeignKey("recommendation.id"))
recommendation = db.relationship(
"RecommendationModel", backref=db.backref("decisions", lazy="dynamic")
)
value = db.Column(db.String(2000))
def __repr__(self):
return self.value
class AdminView(View):
column_default_sort = "value"
column_filters = ["representative.name", "recommendation.name"]
admin.add_view(AdminView(DecisionModel, db.session, name="Decision", category="CRUD"))

View File

@ -1,7 +1,5 @@
# encoding: utf-8
from slugify import slugify
from app import admin, db
from app.model.model import Model, View
@ -9,18 +7,19 @@ from app.model.model import Model, View
class EntityModel(db.Model, Model):
__tablename__ = "entity"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
code = db.Column(db.String(2000))
picture = db.Column(db.String(2000))
parent_id = db.Column(db.Integer, db.ForeignKey("entity.id"))
children = db.relationship(
"EntityModel", backref=db.backref("parent", remote_side=id)
)
name = db.Column(db.String(500))
slug = db.Column(db.String(500))
reference = db.Column(db.String(200))
source = db.Column(db.String(200))
type_id = db.Column(db.Integer, db.ForeignKey("type.id"))
type = db.relationship("TypeModel")
start = db.Column(db.DateTime)
end = db.Column(db.DateTime)
country_id = db.Column(db.Integer, db.ForeignKey("country.id"))
country = db.relationship("CountryModel")
parent_id = db.Column(db.Integer, db.ForeignKey("entity.id"))
parent = db.relationship("EntityModel")
start = db.Column(db.Date)
end = db.Column(db.Date)
active = db.Column(db.Boolean)
def __repr__(self):
return self.name
@ -28,10 +27,6 @@ class EntityModel(db.Model, Model):
class AdminView(View):
column_default_sort = "name"
column_filters = ["name", "code", "type.name", "country.name"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
column_filters = ["name", "reference", "parent.name", "start", "end", "active"]
admin.add_view(AdminView(EntityModel, db.session, name="Entity", category="CRUD"))

View File

@ -1,30 +0,0 @@
# encoding: utf-8
from slugify import slugify
from app import admin, db
from app.model.model import Model, View
class MatterModel(db.Model, Model):
__tablename__ = "matter"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000), unique=True)
description = db.Column(db.Text)
active = db.Column(db.Boolean, default=False)
def __repr__(self):
return self.name
class AdminView(View):
column_default_sort = "name"
column_filters = ["name"]
form_columns = ["name", "description", "active"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
admin.add_view(AdminView(MatterModel, db.session, name="Matter", category="CRUD"))

View File

@ -11,28 +11,25 @@ class MembershipModel(db.Model, Model):
representative = db.relationship(
"RepresentativeModel", backref=db.backref("memberships", lazy="dynamic")
)
role_id = db.Column(db.Integer, db.ForeignKey("role.id"))
role = db.relationship(
"RoleModel", backref=db.backref("memberships", lazy="dynamic")
)
start = db.Column(db.DateTime)
end = db.Column(db.DateTime)
entity_id = db.Column(db.Integer, db.ForeignKey("entity.id"))
entity = db.relationship(
"EntityModel", backref=db.backref("memberships", lazy="dynamic")
)
@property
def timestamp(self):
if self.end is None:
return 9999999999 + self.start.timestamp()
else:
return self.end.timestamp()
reference = db.Column(db.String(200))
role_id = db.Column(db.Integer, db.ForeignKey("role.id"))
role = db.relationship("RoleModel")
start = db.Column(db.Date)
end = db.Column(db.Date)
active = db.Column(db.Boolean)
class AdminView(View):
column_default_sort = [("representative.name", False), ("entity.name", False)]
column_filters = ["representative.name", "entity.name", "role.name"]
column_filters = [
"entity.name", "entity.type.name", "representative.full_name", "role.name",
"start", "end", "active",
]
admin.add_view(AdminView(MembershipModel, db.session, name="Membership", category="CRUD"))
admin.add_view(
AdminView(MembershipModel, db.session, name="Membership", category="CRUD")
)

View File

@ -1,6 +1,6 @@
# encoding: utf-8
from datetime import datetime
from datetime import datetime, date
from flask import redirect, request, url_for
from flask_admin.contrib.sqla import ModelView
@ -11,14 +11,14 @@ from app import db
class View(ModelView):
def is_accessible(self):
# TODO: develop mode
# TODO: develop mode, refer to current_app.config.debug
return True
if not current_user.is_authenticated:
return False
return current_user.is_authenticated and current_user.admin
def inaccessible_callback(self, name, **kwargs):
# TODO: develop mode
# TODO: develop mode, refer to current_app.config.debug
return redirect(url_for("core.login"))
return redirect(url_for("admin.login", next=request.url))
@ -51,8 +51,8 @@ class Model:
result_dict = {}
for key in self.__table__.columns.keys():
if not key.startswith("_"):
if isinstance(getattr(self, key), datetime):
if isinstance(getattr(self, key), (datetime, date)):
result_dict[key] = getattr(self, key).strftime("%Y-%m-%d")
else:
result_dict[key] = getattr(self, key)
return result_dict
return result_dict

View File

@ -1,43 +0,0 @@
# encoding: utf-8
from slugify import slugify
from app import admin, db
from app.model.model import Model, View
class RecommendationModel(db.Model, Model):
__tablename__ = "recommendation"
id = db.Column(db.Integer, primary_key=True)
matter_id = db.Column(db.Integer, db.ForeignKey("matter.id"))
matter = db.relationship(
"MatterModel", backref=db.backref("recommendations", lazy="dynamic")
)
entity_id = db.Column(db.Integer, db.ForeignKey("entity.id"))
entity = db.relationship(
"EntityModel", backref=db.backref("recommendations", lazy="dynamic")
)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000), unique=True)
code = db.Column(db.String(2000))
date = db.Column(db.Date)
description = db.Column(db.Text)
value = db.Column(db.String(2000))
weight = db.Column(db.Integer, default=1)
def __repr__(self):
return f"{self.name} - {self.value} ({self.weight})"
class AdminView(View):
column_default_sort = "name"
column_filters = ["name", "matter.name"]
form_columns = ["matter", "entity", "name", "code", "date", "description", "value", "weight"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
admin.add_view(
AdminView(RecommendationModel, db.session, name="Recommendation", category="CRUD")
)

View File

@ -2,75 +2,42 @@
from app import admin, db
from app.model.model import Model, View
from app.model.country import CountryModel
from sqlalchemy.ext.hybrid import hybrid_property
class RepresentativeModel(db.Model, Model):
__tablename__ = "representative"
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(2000), unique=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
active = db.Column(db.Boolean, default=False)
picture = db.Column(db.String(2000))
first_name = db.Column(db.String(200))
last_name = db.Column(db.String(200))
birth_date = db.Column(db.Date)
birth_place = db.Column(db.String(200))
birth_region = db.Column(db.String(200))
birth_country_id = db.Column(db.Integer, db.ForeignKey("country.id"))
birth_country = db.relationship(
"CountryModel", foreign_keys="RepresentativeModel.birth_country_id"
)
job = db.Column(db.String(200))
nationality_id = db.Column(db.Integer, db.ForeignKey("country.id"))
nationality = db.relationship(
"CountryModel", foreign_keys=[nationality_id], backref=db.backref("representatives", lazy="dynamic")
"CountryModel", foreign_keys="RepresentativeModel.nationality_id"
)
sex = db.Column(db.String(1))
birth_date = db.Column(db.Date)
birth_place = db.Column(db.String(2000))
job = db.Column(db.String(2000))
picture = db.Column(db.String(200))
@property
def parpol(self):
"""
A representative is maybe part of a political party.
"""
# Active one first
for membership in [membership for membership in self.memberships if membership.end is None]:
if membership.entity.type.code == "PARPOL":
return membership.entity.name
# Else old one
for membership in sorted(self.memberships, key=lambda x: x.start, reverse=True):
if membership.entity.type.code == "PARPOL":
return membership.entity.name
@property
def is_female(self):
return self.sex == "F"
@property
def is_active(self):
"""
A representative is active if she has at least one membership not ended.
"""
for membership in self.memberships:
if membership.end is None:
return True
return False
@property
def score(self):
total = 0
for decision in self.decisions:
if decision.value == decision.recommendation.value:
total += decision.recommendation.weight
else:
total -= decision.recommendation.weight
return total
@hybrid_property
def full_name(self):
return self.first_name + " " + self.last_name
def __repr__(self):
return self.name
return f"{self.first_name.capitalize()} {self.last_name.upper()}"
class AdminView(View):
column_default_sort = "name"
column_exclude_list = ["picture"]
column_filters = ["name", "nationality.name"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
column_default_sort = "last_name"
column_exclude_list = [
"birth_region", "birth_country", "picture",
]
column_filters = ["last_name", "first_name", "nationality.name", ]
admin.add_view(AdminView(RepresentativeModel, db.session, name="Representative", category="CRUD"))

View File

@ -0,0 +1,21 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
class RepresentativeOriginModel(db.Model, Model):
__tablename__ = "representative_origin"
id = db.Column(db.Integer, primary_key=True)
representative_id = db.Column(db.Integer, db.ForeignKey("representative.id"))
representative = db.relationship(
"RepresentativeModel", backref=db.backref("references", lazy="dynamic")
)
source_id = db.Column(db.Integer, db.ForeignKey("source.id"))
source = db.relationship("SourceModel")
reference = db.Column(db.String(200))
admin.add_view(View(
RepresentativeOriginModel, db.session, name="RepresentativeOrigin", category="CRUD"
))

View File

@ -7,20 +7,12 @@ from app.model.model import Model, View
class RoleModel(db.Model, Model):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
code = db.Column(db.String(2000))
code = db.Column(db.String(200))
name = db.Column(db.String(200))
slug = db.Column(db.String(200))
def __repr__(self):
return self.name
class AdminView(View):
column_default_sort = "name"
column_filters = ["name", "code"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)
admin.add_view(AdminView(RoleModel, db.session, name="Role", category="CRUD"))
admin.add_view(View(RoleModel, db.session, name="Role", category="CRUD"))

17
app/model/source.py Normal file
View File

@ -0,0 +1,17 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
class SourceModel(db.Model, Model):
__tablename__ = "source"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200))
slug = db.Column(db.String(200))
def __repr__(self):
return self.name
admin.add_view(View(SourceModel, db.session, name="Source", category="CRUD"))

View File

@ -1,44 +0,0 @@
# encoding: utf-8
from app import admin, db
from app.model.model import Model, View
from app.model.matter import MatterModel
from app.model.representative import RepresentativeModel
class StanceModel(db.Model, Model):
__tablename__ = "stance"
id = db.Column(db.Integer, primary_key=True)
representative_id = db.Column(db.Integer, db.ForeignKey("representative.id"))
representative = db.relationship(
"RepresentativeModel", backref=db.backref("stances", lazy="dynamic")
)
matter_id = db.Column(db.Integer, db.ForeignKey("matter.id"), nullable=True)
matter = db.relationship(
"MatterModel", backref=db.backref("stances", lazy="dynamic")
)
date = db.Column(db.Date)
subject = db.Column(db.String(2000))
extract = db.Column(db.Text)
source_url = db.Column(db.String(2000))
active = db.Column(db.Boolean, default=False)
def __repr__(self):
return self.representative.name + " : " + self.extract[:50]
@property
def extract_html(self):
return "<p>" + self.extract.replace("\n", "</p><p>") + "</p>"
@property
def extract_chapo(self):
return " ".join((self.extract + " ")[:60].split(" ")[:-1])
class AdminView(View):
column_default_sort = ("date", False)
column_exclude_list = ["extract"]
column_filters = ["representative.name", "subject", "date"]
admin.add_view(AdminView(StanceModel, db.session, name="Stance", category="CRUD"))

View File

@ -9,17 +9,16 @@ from app.model.model import Model, View
class TypeModel(db.Model, Model):
__tablename__ = "type"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(2000))
slug = db.Column(db.String(2000))
code = db.Column(db.String(2000))
code = db.Column(db.String(200))
name = db.Column(db.String(500))
slug = db.Column(db.String(500))
def __repr__(self):
return self.name
class AdminView(View):
column_default_sort = "name"
column_filters = ["name", "code"]
form_excluded_columns = ["slug"]
def on_model_change(self, form, model, is_created):
model.slug = slugify(model.name)

63
app/model/user.py Normal file
View File

@ -0,0 +1,63 @@
# encoding: utf-8
import bcrypt
from flask import current_app
from app import admin, db
from app.model.model import Model, View
def get_user(user_id):
return UserModel.query.get(user_id)
class UserModel(db.Model, Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(500), unique=True)
password_hash = db.Column(db.String(128))
email = db.Column(db.String(500), unique=True)
active = db.Column(db.Boolean)
admin = db.Column(db.Boolean)
@property
def password(self):
return self.password_hash
@password.setter
def password(self, password):
self.password_hash = bcrypt.hashpw(
password.encode("utf-8"),
bcrypt.gensalt(rounds=current_app.config["BCRYPT_ROUNDS"]),
)
def check_password(self, password):
return bcrypt.checkpw(password.encode("utf-8"), self.password_hash)
@property
def is_active(self):
return self.active or False
@property
def is_anonymous(self):
return self.id is None
@property
def is_authenticated(self):
return self.id is not None
def get_id(self):
return str(self.id)
class AdminView(View):
column_default_sort = "login"
column_exclude_list = ["password_hash", ]
def on_model_change(self, form, model, is_created):
if len(form.password_hash.data) < 128:
model.password = form.password_hash.data
admin.add_view(AdminView(UserModel, db.session, name="User"))

View File

@ -2,9 +2,9 @@
from app import admin
from app.controller.admin import admin_routes
from app.controller.api.country import CountriesApi, CountryApi
from app.controller.api.representative import RepresentativesApi, RepresentativeApi
from app.controller.core import Core
from app.controller.representative import Representative
# Adding admin endpoints
@ -14,15 +14,13 @@ for route in admin_routes:
# Listing normal endpoints
routes = [
("/", Core.as_view("home")),
("/representative/<int:representative_id>", Core.as_view("representative")),
("/about", Core.as_view("about")),
("/who", Core.as_view("who")),
("/representative/<int:representative_id>", Representative.as_view("view")),
]
# Listing API endpoints
apis = [
('/api/country', CountriesApi),
('/api/country/<country_id>', CountryApi),
('/api/representative', RepresentativesApi),
('/api/representative/<representative_id>', RepresentativeApi),
]

View File

@ -9,4 +9,4 @@
</ul>
{%endif%}
</div>
{%endmacro%}
{%endmacro%}

View File

@ -7,7 +7,6 @@
<li>Find a representative</li>
<li>Find a matter</li>
-->
<li><a href="">{{_("Ajouter une prise de position")}}</a></li>
</ul>
</nav>
</div>
@ -19,4 +18,4 @@
</div>
-->
<hr />
</header>
</header>

30
app/view/stance/add.html Normal file
View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<div id="main">
<h1>{{_("Ajouter une prise de position")}}</h1>
<form method="POST" action="{{url_for('stance.add')}}">
{{g.form.hidden_tag()}}
{{g.form.representative()}}
{{g.form.matter()}}
<div class="field">
<label>{{_("Représentant :")}}</label> <input type="text" />
<span class="field-description">{{_("Le représentant ayant pris cette position")}}</span>
</div>
<div class="field">
<label>{{_("Dossier :")}}</label> <input type="text" />
<span class="field-description">{{_("Le dossier lié à cette prise de position. Ce champ reste optionnel.")}}</span>
</div>
{{render_field(g.form.date)}}
{{render_field(g.form.source_url)}}
{{render_field(g.form.extract)}}
<input type="submit" value="{{_('Ajouter')}}" class="btn" />
<a href="{{url_for('core.home')}}" class="btn">{{_("Annuler")}}</a>
</form>
</div>
{% endblock %}

View File

@ -1,37 +0,0 @@
# encoding: utf-8
"""
Minimal configuration able to run but maybe not as you want it.
"""
import os
DEBUG = False
HOST = "0.0.0.0"
PORT = 5000
SECRET_KEY = "No secret key"
JINJA_ENV = {
"TRIM_BLOCKS": True,
"LSTRIP_BLOCKS": True,
}
# defining base directory
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
# defining database URI
# MySQL example
# SQLALCHEMY_DATABASE_URI = "mysql://username:password@server/db"
# SQLite example
# SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# defining Babel settings
BABEL_DEFAULT_LOCALE = "fr"
# Languages available
AVAILABLE_LANGUAGES = {
"en": "English",
"fr": "Français",
}
API_PER_PAGE = 30

View File

@ -1 +1 @@
Generic single-database configuration.
Single-database configuration for Flask.

View File

@ -11,7 +11,7 @@
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
@ -34,6 +34,11 @@ level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)

View File

@ -3,8 +3,6 @@ from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from flask import current_app
from alembic import context
@ -24,7 +22,8 @@ logger = logging.getLogger('alembic.env')
# target_metadata = mymodel.Base.metadata
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
@ -72,11 +71,7 @@ def run_migrations_online():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
connectable = current_app.extensions['migrate'].db.get_engine()
with connectable.connect() as connection:
context.configure(

View File

@ -0,0 +1,32 @@
"""empty message
Revision ID: 0df00d383cb1
Revises: c968d84e9996
Create Date: 2022-05-27 21:31:07.974570
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0df00d383cb1'
down_revision = 'c968d84e9996'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('country', sa.Column('slug', sa.String(length=200), nullable=True))
op.add_column('entity', sa.Column('slug', sa.String(length=200), nullable=True))
op.add_column('role', sa.Column('slug', sa.String(length=200), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('role', 'slug')
op.drop_column('entity', 'slug')
op.drop_column('country', 'slug')
# ### end Alembic commands ###

View File

@ -0,0 +1,44 @@
"""empty message
Revision ID: 49d256e40a4c
Revises:
Create Date: 2022-05-26 13:01:00.915135
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '49d256e40a4c'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('representative',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('login', sa.String(length=500), nullable=True),
sa.Column('password_hash', sa.String(length=128), nullable=True),
sa.Column('email', sa.String(length=500), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.Column('admin', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('login')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user')
op.drop_table('representative')
# ### end Alembic commands ###

View File

@ -0,0 +1,43 @@
"""empty message
Revision ID: 6a205c6b23bb
Revises: bc290d035406
Create Date: 2022-05-27 11:12:44.242389
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6a205c6b23bb'
down_revision = 'bc290d035406'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('entity',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('membership',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('entity_id', sa.Integer(), nullable=True),
sa.Column('start', sa.Date(), nullable=True),
sa.Column('end', sa.Date(), nullable=True),
sa.ForeignKeyConstraint(['entity_id'], ['entity.id'], ),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('membership')
op.drop_table('entity')
# ### end Alembic commands ###

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: ac34a07322f6
Revises: b83b29dc60b0
Create Date: 2021-07-21 15:36:01.454538
Revision ID: 6dcc965ab0d4
Revises: f448ec882889
Create Date: 2022-05-28 19:35:20.965988
"""
from alembic import op
@ -10,19 +10,19 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ac34a07322f6'
down_revision = 'b83b29dc60b0'
revision = '6dcc965ab0d4'
down_revision = 'f448ec882889'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('representative', sa.Column('active', sa.Boolean(), nullable=True))
op.add_column('membership', sa.Column('active', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('representative', 'active')
op.drop_column('membership', 'active')
# ### end Alembic commands ###

View File

@ -1,28 +0,0 @@
"""empty message
Revision ID: 8b260b23ba3a
Revises: e0001237a466
Create Date: 2021-07-21 14:03:53.023739
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8b260b23ba3a'
down_revision = 'e0001237a466'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('representative', sa.Column('code', sa.String(length=2000), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('representative', 'code')
# ### end Alembic commands ###

View File

@ -0,0 +1,48 @@
"""empty message
Revision ID: aa24a81be355
Revises: 0df00d383cb1
Create Date: 2022-05-28 15:52:36.923253
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'aa24a81be355'
down_revision = '0df00d383cb1'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('type',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(length=200), nullable=True),
sa.Column('name', sa.String(length=200), nullable=True),
sa.Column('slug', sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.add_column('entity', sa.Column('parent_id', sa.Integer(), nullable=True))
op.add_column('entity', sa.Column('reference', sa.String(length=200), nullable=True))
op.add_column('entity', sa.Column('source', sa.String(length=200), nullable=True))
op.add_column('entity', sa.Column('type_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'entity', 'type', ['type_id'], ['id'])
op.create_foreign_key(None, 'entity', 'entity', ['parent_id'], ['id'])
op.drop_column('membership', 'source')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('membership', sa.Column('source', mysql.VARCHAR(length=200), nullable=True))
op.drop_constraint(None, 'entity', type_='foreignkey')
op.drop_constraint(None, 'entity', type_='foreignkey')
op.drop_column('entity', 'type_id')
op.drop_column('entity', 'source')
op.drop_column('entity', 'reference')
op.drop_column('entity', 'parent_id')
op.drop_table('type')
# ### end Alembic commands ###

View File

@ -1,30 +0,0 @@
"""empty message
Revision ID: b83b29dc60b0
Revises: 8b260b23ba3a
Create Date: 2021-07-21 14:04:54.107335
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b83b29dc60b0'
down_revision = '8b260b23ba3a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('representative_slug_key', 'representative', type_='unique')
op.create_unique_constraint(None, 'representative', ['code'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'representative', type_='unique')
op.create_unique_constraint('representative_slug_key', 'representative', ['slug'])
# ### end Alembic commands ###

View File

@ -0,0 +1,34 @@
"""empty message
Revision ID: bc290d035406
Revises: 49d256e40a4c
Create Date: 2022-05-26 13:29:55.124869
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'bc290d035406'
down_revision = '49d256e40a4c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('representative', sa.Column('first_name', sa.String(length=200), nullable=True))
op.add_column('representative', sa.Column('last_name', sa.String(length=200), nullable=True))
op.add_column('representative', sa.Column('picture', sa.String(length=200), nullable=True))
op.drop_column('representative', 'name')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('representative', sa.Column('name', mysql.VARCHAR(length=200), nullable=True))
op.drop_column('representative', 'picture')
op.drop_column('representative', 'last_name')
op.drop_column('representative', 'first_name')
# ### end Alembic commands ###

View File

@ -0,0 +1,65 @@
"""empty message
Revision ID: c968d84e9996
Revises: 6a205c6b23bb
Create Date: 2022-05-27 17:56:50.589477
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c968d84e9996'
down_revision = '6a205c6b23bb'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('country',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=True),
sa.Column('official_state_name', sa.String(length=200), nullable=True),
sa.Column('alpha_2', sa.String(length=2), nullable=True),
sa.Column('alpha_3', sa.String(length=3), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.add_column('membership', sa.Column('source', sa.String(length=200), nullable=True))
op.add_column('membership', sa.Column('reference', sa.String(length=200), nullable=True))
op.add_column('membership', sa.Column('role_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'membership', 'role', ['role_id'], ['id'])
op.add_column('representative', sa.Column('birth_date', sa.Date(), nullable=True))
op.add_column('representative', sa.Column('birth_place', sa.String(length=200), nullable=True))
op.add_column('representative', sa.Column('birth_region', sa.String(length=200), nullable=True))
op.add_column('representative', sa.Column('birth_country_id', sa.Integer(), nullable=True))
op.add_column('representative', sa.Column('job', sa.String(length=200), nullable=True))
op.add_column('representative', sa.Column('nationality_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'representative', 'country', ['birth_country_id'], ['id'])
op.create_foreign_key(None, 'representative', 'country', ['nationality_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'representative', type_='foreignkey')
op.drop_constraint(None, 'representative', type_='foreignkey')
op.drop_column('representative', 'nationality_id')
op.drop_column('representative', 'job')
op.drop_column('representative', 'birth_country_id')
op.drop_column('representative', 'birth_region')
op.drop_column('representative', 'birth_place')
op.drop_column('representative', 'birth_date')
op.drop_constraint(None, 'membership', type_='foreignkey')
op.drop_column('membership', 'role_id')
op.drop_column('membership', 'reference')
op.drop_column('membership', 'source')
op.drop_table('role')
op.drop_table('country')
# ### end Alembic commands ###

View File

@ -0,0 +1,32 @@
"""empty message
Revision ID: cd76771172a8
Revises: f238158c8ad6
Create Date: 2022-05-28 17:21:55.500700
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'cd76771172a8'
down_revision = 'f238158c8ad6'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('entity', sa.Column('start', sa.Date(), nullable=True))
op.add_column('entity', sa.Column('end', sa.Date(), nullable=True))
op.add_column('entity', sa.Column('active', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('entity', 'active')
op.drop_column('entity', 'end')
op.drop_column('entity', 'start')
# ### end Alembic commands ###

View File

@ -1,178 +0,0 @@
"""empty message
Revision ID: e0001237a466
Revises:
Create Date: 2021-07-16 19:43:05.321519
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e0001237a466'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('country',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('code', sa.String(length=2000), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('matter',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('code', sa.String(length=2000), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('type',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('code', sa.String(length=2000), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('address',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('country_id', sa.Integer(), nullable=True),
sa.Column('number', sa.String(length=2000), nullable=True),
sa.Column('street', sa.String(length=2000), nullable=True),
sa.Column('miscellaneous', sa.String(length=2000), nullable=True),
sa.Column('city', sa.String(length=2000), nullable=True),
sa.Column('zipcode', sa.String(length=2000), nullable=True),
sa.Column('building', sa.String(length=2000), nullable=True),
sa.Column('floor', sa.String(length=2000), nullable=True),
sa.Column('stair', sa.String(length=2000), nullable=True),
sa.Column('office', sa.String(length=2000), nullable=True),
sa.Column('latitude', sa.String(length=2000), nullable=True),
sa.Column('longitude', sa.String(length=2000), nullable=True),
sa.ForeignKeyConstraint(['country_id'], ['country.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('entity',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('code', sa.String(length=2000), nullable=True),
sa.Column('picture', sa.String(length=2000), nullable=True),
sa.Column('type_id', sa.Integer(), nullable=True),
sa.Column('start', sa.DateTime(), nullable=True),
sa.Column('end', sa.DateTime(), nullable=True),
sa.Column('country_id', sa.Integer(), nullable=True),
sa.Column('parent_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['country_id'], ['country.id'], ),
sa.ForeignKeyConstraint(['parent_id'], ['entity.id'], ),
sa.ForeignKeyConstraint(['type_id'], ['type.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('representative',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('picture', sa.String(length=2000), nullable=True),
sa.Column('nationality_id', sa.Integer(), nullable=True),
sa.Column('sex', sa.String(length=1), nullable=True),
sa.Column('birth_date', sa.Date(), nullable=True),
sa.Column('birth_place', sa.String(length=2000), nullable=True),
sa.Column('job', sa.String(length=2000), nullable=True),
sa.ForeignKeyConstraint(['nationality_id'], ['country.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('contact',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('address_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('value', sa.String(length=2000), nullable=True),
sa.ForeignKeyConstraint(['address_id'], ['address.id'], ),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('membership',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.Column('start', sa.DateTime(), nullable=True),
sa.Column('end', sa.DateTime(), nullable=True),
sa.Column('entity_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['entity_id'], ['entity.id'], ),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('recommendation',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('matter_id', sa.Integer(), nullable=True),
sa.Column('entity_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(length=2000), nullable=True),
sa.Column('slug', sa.String(length=2000), nullable=True),
sa.Column('code', sa.String(length=2000), nullable=True),
sa.Column('date', sa.Date(), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('value', sa.String(length=2000), nullable=True),
sa.Column('weight', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['entity_id'], ['entity.id'], ),
sa.ForeignKeyConstraint(['matter_id'], ['matter.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('stance',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('matter_id', sa.Integer(), nullable=True),
sa.Column('date', sa.DateTime(), nullable=True),
sa.Column('subject', sa.String(length=2000), nullable=True),
sa.Column('extract', sa.Text(), nullable=True),
sa.Column('source_url', sa.String(length=2000), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['matter_id'], ['matter.id'], ),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('decision',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('recommendation_id', sa.Integer(), nullable=True),
sa.Column('value', sa.String(length=2000), nullable=True),
sa.ForeignKeyConstraint(['recommendation_id'], ['recommendation.id'], ),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('decision')
op.drop_table('stance')
op.drop_table('recommendation')
op.drop_table('membership')
op.drop_table('contact')
op.drop_table('representative')
op.drop_table('entity')
op.drop_table('address')
op.drop_table('type')
op.drop_table('role')
op.drop_table('matter')
op.drop_table('country')
# ### end Alembic commands ###

View File

@ -0,0 +1,58 @@
"""empty message
Revision ID: f238158c8ad6
Revises: aa24a81be355
Create Date: 2022-05-28 16:41:01.047513
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'f238158c8ad6'
down_revision = 'aa24a81be355'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('entity', 'name',
existing_type=mysql.VARCHAR(length=200),
type_=sa.String(length=500),
existing_nullable=True)
op.alter_column('entity', 'slug',
existing_type=mysql.VARCHAR(length=200),
type_=sa.String(length=500),
existing_nullable=True)
op.alter_column('type', 'name',
existing_type=mysql.VARCHAR(length=200),
type_=sa.String(length=500),
existing_nullable=True)
op.alter_column('type', 'slug',
existing_type=mysql.VARCHAR(length=200),
type_=sa.String(length=500),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('type', 'slug',
existing_type=sa.String(length=500),
type_=mysql.VARCHAR(length=200),
existing_nullable=True)
op.alter_column('type', 'name',
existing_type=sa.String(length=500),
type_=mysql.VARCHAR(length=200),
existing_nullable=True)
op.alter_column('entity', 'slug',
existing_type=sa.String(length=500),
type_=mysql.VARCHAR(length=200),
existing_nullable=True)
op.alter_column('entity', 'name',
existing_type=sa.String(length=500),
type_=mysql.VARCHAR(length=200),
existing_nullable=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,45 @@
"""empty message
Revision ID: f448ec882889
Revises: cd76771172a8
Create Date: 2022-05-28 18:53:26.059567
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f448ec882889'
down_revision = 'cd76771172a8'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('source',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=True),
sa.Column('slug', sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('representative_origin',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('representative_id', sa.Integer(), nullable=True),
sa.Column('source_id', sa.Integer(), nullable=True),
sa.Column('reference', sa.String(length=200), nullable=True),
sa.ForeignKeyConstraint(['representative_id'], ['representative.id'], ),
sa.ForeignKeyConstraint(['source_id'], ['source.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.add_column('role', sa.Column('code', sa.String(length=200), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('role', 'code')
op.drop_table('representative_origin')
op.drop_table('source')
# ### end Alembic commands ###

View File

@ -1,5 +0,0 @@
[build-system]
requires = ["setuptools", "wheel"]
[tool.black]
exclude = "migrations/"

View File

@ -1,3 +0,0 @@
black
pytest
pytest-black

View File

@ -7,5 +7,7 @@ flask_migrate
flask_restful
flask_sqlalchemy
flask_wtf
python-dotenv
python-slugify
requests
mysqlclient

View File

@ -13,38 +13,34 @@ from app.routes import apis, routes
from command import commands
app = flask.Flask(__name__, template_folder="app/view")
app.config.from_object("config")
try:
app.config.from_object(f"config-{app.config['ENV']}")
except Exception as e:
print(e)
application = flask.Flask(__name__, template_folder="app/view")
application.config.from_object("settings")
if "JINJA_ENV" in app.config:
app.jinja_env.trim_blocks = app.config["JINJA_ENV"]["TRIM_BLOCKS"]
app.jinja_env.lstrip_blocks = app.config["JINJA_ENV"]["LSTRIP_BLOCKS"]
if "JINJA_ENV" in application.config:
application.jinja_env.trim_blocks = application.config["JINJA_ENV"]["TRIM_BLOCKS"]
application.jinja_env.lstrip_blocks = application.config["JINJA_ENV"]["LSTRIP_BLOCKS"]
# Loading routes
for route in routes:
if len(route) < 3:
app.add_url_rule(route[0], route[1].__name__, route[1], methods=["GET"])
application.add_url_rule(route[0], route[1].__name__, route[1], methods=["GET"])
else:
app.add_url_rule(route[0], route[1].__name__, route[1], methods=route[2])
application.add_url_rule(route[0], route[1].__name__, route[1], methods=route[2])
# Loading API routes
for route in apis:
api.add_resource(route[1], route[0])
# Initialisation of extensions
admin.init_app(app)
api.init_app(app)
babel.init_app(app)
db.init_app(app)
login_manager.init_app(app)
migrate.init_app(app, db)
admin.init_app(application)
api.init_app(application)
babel.init_app(application)
db.init_app(application)
login_manager.init_app(application)
migrate.init_app(application, db)
# Manage commands
for command in commands:
app.cli.add_command(command)
application.cli.add_command(command)
# Manage locale

21
settings.py Normal file
View File

@ -0,0 +1,21 @@
# encoding: utf-8
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
DEBUG = os.environ.get("DEBUG", False)
SECRET_KEY = os.environ.get("SECRET_KEY", "Choose a secret key")
JINJA_ENV = {
"TRIM_BLOCKS": True,
"LSTRIP_BLOCKS": True,
}
SQLALCHEMY_DATABASE_URI = os.environ.get("SQLALCHEMY_DATABASE_URI", "sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3"))
SQLALCHEMY_TRACK_MODIFICATIONS = False
BCRYPT_ROUNDS = os.environ.get("BCRYPT_ROUNDS", 15)
BABEL_DEFAULT_LOCALE = os.environ.get("BABEL_DEFAULT_LOCALE", "fr")
AVAILABLE_LANGUAGES = {
"fr": "Français",
"en": "English",
}
API_PER_PAGE = int(os.environ.get("API_PER_PAGE", 10))