Skip to content

Commit

Permalink
Merge branch 'master' of github.com:nursix/eden
Browse files Browse the repository at this point in the history
  • Loading branch information
nursix committed Jan 27, 2021
2 parents 88dcc08 + 29f4db2 commit ca35e5f
Show file tree
Hide file tree
Showing 13 changed files with 328 additions and 41 deletions.
69 changes: 69 additions & 0 deletions controllers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,71 @@ def organisation():

return items

# -----------------------------------------------------------------------------
def page():
"""
Show a custom CMS page
"""

if not settings.get_cms_expose_pages():
raise HTTP(403, "CMS pages disabled")

try:
page = request.args[0]
except:
raise HTTP(400, "Page not specified")

# Find a post with the given page name that is linked to this controller:
ctable = s3db.cms_post
ltable = s3db.cms_post_module
join = ltable.on((ltable.post_id == ctable.id) & \
(ltable.module == "default") & \
(ltable.resource == "page") & \
(ltable.deleted == False))

query = auth.s3_accessible_query("read", ctable) & \
(ctable.name == page) & \
(ctable.deleted == False)
row = db(query).select(ctable.id,
ctable.title,
ctable.body,
join = join,
cache = s3db.cache,
limitby = (0, 1),
).first()
try:
title = row.title
except:
raise HTTP(404, "Page not found in CMS")

if row.body:
from s3compat import StringIO
try:
body = current.response.render(StringIO(row.body), {})
except:
body = row.body
else:
body = ""
item = DIV(XML(body), _class="cms-item")

if auth.s3_has_role("ADMIN"):
# Add edit-action
item.append(BR())
item.append(A(current.T("Edit"),
_href = URL(c="cms", f="post",
args = [row.id, "update"],
vars = {"page": page},
),
_class = "action-btn",
))

response.title = title
_custom_view("page")

return {#"title": title, # Page would normally render the title itself?
"item": item,
}

# -----------------------------------------------------------------------------
def person():
"""
Expand Down Expand Up @@ -1422,6 +1487,10 @@ def user():
title = response.title = T("User Profile")
form = auth.profile()

elif arg == "consent":
title = response.title = T("Consent")
form = auth.consent()

elif arg == "options.s3json":
# Used when adding organisations from registration form
return s3_rest_controller(prefix="auth", resourcename="user")
Expand Down
7 changes: 5 additions & 2 deletions languages/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@
'Connection': 'Verbindung',
'Consent Options': 'Einwilligungsfragen',
'Consent Tracking': 'Einwilligungen',
'Consent registered': 'Einwilligung registriert',
'Consent required': 'Einwilligung erforderlich',
'Consent': 'Einwilligung',
'Consignment Note': 'Warenbegleitschein',
Expand Down Expand Up @@ -3502,10 +3503,12 @@
'No options available': 'Keine Optionen verfügbar',
'No payments specified': 'Keine Auszahlungen angegeben',
'No peers currently registered': 'Zurzeit sind keine Peers registriert',
'No pending consent questions for the current user': 'Keine offenen Einwilligungs-Fragen für den aktuellen Benutzer',
'No pending payments': 'Keine anstehenden Auszahlungen',
'No pending registrations found': 'Keine anstehenden Registrierungen gefunden',
'No pending registrations matching the query': 'Die Abfrage lieferte keine keine anstehenden Registrierungen',
'No person found with this ID number': 'Keine Person mit dieser ID Nummer gefunden',
'No person record for the current user': 'Kein Personen-Datensatz für den aktuellen Benutzer gefunden',
'No person record found for current user.': 'Kein Personendatensatz für den aktuellen Benutzer gefunden.',
'No picture available': 'Kein Bild verfügbar',
'No problem group defined yet': 'Noch keine Problem-Gruppe definiert',
Expand Down Expand Up @@ -3687,14 +3690,14 @@
'Optional. The name of an element whose contents should be put into Popups.': 'Optional. Name eines Elements, dessen Inhalt in Dialogfenstern angezeigt wird.',
'Optional. The name of the schema. In Geoserver this has the form http://host_name/geoserver/wfs/DescribeFeatureType?version=1.1.0&;typename=workspace_name:layer_name.': 'Optional. Name des Schemas. Bei Geoserver wird das Format http://host_name/geoserver/wfs/DescribeFeatureType?version=1.1.0&;typename=workspace_name:layer_name verwendet.',
'Options': 'Optionen',
'Organization Group': 'Organisationsgruppe',
'Organization could not be notified': 'Organisation konnte nicht benachrichtigt werden',
'Organization Details': 'Details zur Organisation',
'Organization Domains': 'Organisationsdomains',
'Organization Group': 'Organisationsgruppe',
'Organization Registry': 'Organisationsdatenbank',
'Organization Type': 'Organisationstyp',
'Organization Types': 'Organisationstypen',
'Organization added': 'Organisation hinzugefügt',
'Organization could not be notified': 'Organisation konnte nicht benachrichtigt werden',
'Organization deleted': 'Organisation gelöscht',
'Organization updated': 'Organisation aktualisiert',
'Organization': 'Organisation',
Expand Down
3 changes: 1 addition & 2 deletions languages/en-gb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
{
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
"A volunteer is defined as active if they've participated in an average of 8 or more hours of Program work or Trainings per month in the last year": "A volunteer is defined as active if they've participated in an average of 8 or more hours of Programme work or Trainings per month in the last year",
'Ability to customize the list of details tracked at a Shelter': 'Ability to customise the list of details tracked at a Shelter',
'Ability to customize the list of human resource tracked at a Shelter': 'Ability to customise the list of human resource tracked at a Shelter',
Expand Down Expand Up @@ -134,7 +135,6 @@
'Organization Domain Details': 'Organisation Domain Details',
'Organization Domain updated': 'Organisation Domain updated',
'Organization Domains': 'Organisation Domains',
'Organization Domains': 'Organisation Domains',
'Organization Name': 'Organisation Name',
'Organization not found': 'Organisation not found',
'Organization removed from Project': 'Organisation removed from Project',
Expand Down Expand Up @@ -180,7 +180,6 @@
'Project Organization Details': 'Project Organisation Details',
'Project Organization updated': 'Project Organisation updated',
'Project Organizations': 'Project Organisations',
'Project Organizations': 'Project Organisations',
'Public Sector Organization': 'Public Sector Organisation',
'Register as an Organization or Agency': 'Register as an Organisation or Agency',
'Request Canceled': 'Request Cancelled',
Expand Down
146 changes: 133 additions & 13 deletions modules/s3/s3aaa.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,19 +792,30 @@ def login(self,

# How to continue
if next is DEFAULT:
if deployment_settings.has_module("setup") and \
deployment_settings.get_setup_wizard_questions() and \
self.s3_has_role("ADMIN"):
itable = current.s3db.setup_instance
instance = db(itable.url == "https://%s" % request.env.HTTP_HOST).select(itable.id,
itable.deployment_id,
itable.configured,
limitby = (0, 1)
).first()
if instance and not instance.configured:
# Run Configuration Wizard
next = URL(c="setup", f="deployment",
args = [instance.deployment_id, "instance", instance.id, "wizard"])
is_admin = self.s3_has_role("ADMIN")
if is_admin:
# Setup
if deployment_settings.has_module("setup") and \
deployment_settings.get_setup_wizard_questions():
itable = current.s3db.setup_instance
instance = db(itable.url == "https://%s" % request.env.HTTP_HOST).select(itable.id,
itable.deployment_id,
itable.configured,
limitby = (0, 1)
).first()
if instance and not instance.configured:
# Run Configuration Wizard
next = URL(c="setup", f="deployment",
args = [instance.deployment_id, "instance", instance.id, "wizard"])

elif accepted_form:
# Check for pending consent upon login?
pending_consent = deployment_settings.get_auth_consent_check()
if callable(pending_consent):
pending_consent = pending_consent()
if pending_consent:
next = URL(c="default", f="user", args=["consent"])

if next is DEFAULT:
if deployment_settings.get_auth_login_next_always():
next = deployment_settings.get_auth_login_next()
Expand All @@ -816,6 +827,7 @@ def login(self,
next = deployment_settings.get_auth_login_next()
if callable(next):
next = next()

if settings.login_form == self:
if accepted_form:
if onaccept:
Expand Down Expand Up @@ -1172,6 +1184,114 @@ def login_user(self, user):
person_id).set_location(closestpoint,
timestmp = request.utcnow)

# -------------------------------------------------------------------------
def consent(self):
"""
Consent question form, e.g.
- when consent requires renewal, or
- new consent questions need to be asked, or
- user has been added by ADMIN and shall give consent upon login
- ...
NB: this form cannot meaningfully prevent the user from simply
bypassing the question and navigating away. To prevent the
user from accessing functionality for which consent is
mandatory, the respective controllers must check for consent
using auth_Consent.has_consented, and refuse if not given
(though they can still redirect to this form where useful)
"""

T = current.T

request = current.request
response = current.response
session = current.session
settings = current.deployment_settings

next_url = request.vars.get("_next")
if not next_url:
next_url = settings.get_auth_login_next()
if callable(next_url):
next_url = next_url()
if not next_url:
next_url = URL(c = "default", f = "index")

# Requires login
if not self.s3_logged_in():
session.error = T("Authentication required")
redirect(URL(c = "default", f = "user",
args = ["login"],
vars = {"_next": URL(args=current.request.args)},
))

# Requires person record
person_id = self.s3_logged_in_person()
if not person_id:
session.error = T("No person record for the current user")
redirect(next_url)

# Get all pending consent questions for the current user
pending_consent = settings.get_auth_consent_check()
if callable(pending_consent):
pending_consent = pending_consent()
if not pending_consent:
session.warning = T("No pending consent questions for the current user")
redirect(next_url)
else:
response.warning = T("Consent required")

# Instantiate Consent Tracker
consent = current.s3db.auth_Consent(processing_types=pending_consent)

# Form fields
formfields = [Field("consent",
label = T("Consent"),
widget = consent.widget,
),
]
# Generate labels (and mark required fields in the process)
labels, has_required = s3_mark_required(formfields)
response.s3.has_required = has_required

# Form buttons
SUBMIT = T("Submit")
buttons = [INPUT(_type = "submit",
_value = SUBMIT,
),
]

# Construct the form
response.form_label_separator = ""
form = SQLFORM.factory(table_name = "auth_consent",
record = None,
hidden = {"_next": request.vars._next},
labels = labels,
separator = "",
showid = False,
submit_button = SUBMIT,
delete_label = self.messages.delete_label,
formstyle = settings.get_ui_formstyle(),
buttons = buttons,
*formfields)

# Identify form for CSS
form.add_class("auth_consent")

if form.accepts(current.request.vars,
current.session,
formname = "consent",
):

consent.track(person_id, form.vars.get("consent"))
session.confirmation = T("Consent registered")
redirect(next_url)

# Remind the user that form should be submitted even if they didn't
# enter anything:
response.s3.jquery_ready.append('''S3SetNavigateAwayConfirm();''')

return form

# -------------------------------------------------------------------------
def register(self,
next = DEFAULT,
Expand Down
14 changes: 14 additions & 0 deletions modules/s3cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,14 @@ def get_auth_consent_tracking(self):
""" Expose options to track user consent """
return self.auth.get("consent_tracking", False)

def get_auth_consent_check(self):
"""
Ask for consent renewal upon login
- a function that returns a list of processing type codes for
which the user shall renew their consent after login
"""
return self.auth.get("consent_check", None)

def get_auth_registration_volunteer(self):
""" Redirect the newly-registered user to their volunteer details page """
return self.auth.get("registration_volunteer", False)
Expand Down Expand Up @@ -3308,6 +3316,12 @@ def get_cap_area_default(self):
# -------------------------------------------------------------------------
# CMS: Content Management System
#
def get_cms_expose_pages(self):
"""
Global control over default/page controller
"""
return self.cms.get("expose_pages", False)

def get_cms_bookmarks(self):
"""
Whether to allow users to bookmark Posts in News feed
Expand Down
35 changes: 35 additions & 0 deletions modules/s3db/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,41 @@ def has_consented(cls, person_id, code):

return row is not None

# -------------------------------------------------------------------------
def pending_responses(self, person_id):
"""
Identify all processing types for which a person has not
responded to the updated consent questions, or where their
previously given consent has expired
@param person_id: the person ID
@returns: list of processing type codes
"""

# Get all current consent options for the given processing types
options = self.extract()
option_ids = {spec["id"] for spec in options.values()}

# Find all responses of this person to these options
today = current.request.utcnow.date()
ctable = current.s3db.auth_consent
query = (ctable.person_id == person_id) & \
(ctable.option_id.belongs(option_ids)) & \
((ctable.consenting == False) | \
(ctable.expires_on == None) | \
(ctable.expires_on > today)) & \
(ctable.deleted == False)
rows = current.db(query).select(ctable.option_id)

# Identify any pending responses
responded = {row.option_id for row in rows}
pending = []
for code, spec in options.items():
if spec["id"] not in responded:
pending.append(code)

return pending

# -------------------------------------------------------------------------
@classmethod
def consent_query(cls, table, code, field=None):
Expand Down
Loading

0 comments on commit ca35e5f

Please sign in to comment.