Skip to content

Commit

Permalink
Merge branch 'develop' into postgres-full-text
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrillkuettel committed Jul 10, 2024
2 parents 46b7685 + d4d38d6 commit 57633f4
Show file tree
Hide file tree
Showing 43 changed files with 1,261 additions and 1,002 deletions.
27 changes: 22 additions & 5 deletions src/privatim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
__version__ = '0.0.0'


from typing import Any, TYPE_CHECKING

from privatim.views.profile import user_pic_url

from typing import Any, TYPE_CHECKING, Iterable
if TYPE_CHECKING:
from _typeshed.wsgi import WSGIApplication
from privatim.cli.upgrade import UpgradeContext
Expand Down Expand Up @@ -69,7 +66,14 @@ def includeme(config: Configurator) -> None:
config.set_default_csrf_options(require_csrf=True)

config.add_request_method(authenticated_user, 'user', property=True)
config.add_request_method(user_pic_url, 'user_pic', property=True)

def profile_pic(request: 'IRequest') -> str:
user = request.user
if not user:
return ''
return request.route_url('download_general_file', id=user.picture.id)

config.add_request_method(profile_pic, 'profile_pic', property=True)
config.add_request_method(MessageQueue, 'messages', reify=True)

def add_action_menu_entry(
Expand All @@ -90,6 +94,19 @@ def add_action_menu_entry(
reify=True
)

def add_action_menu_entries(
request: 'IRequest',
entries: Iterable[tuple[str, str]],
) -> None:
if not hasattr(request, 'action_menu_entries'):
request.action_menu_entries = []
for title, url in entries:
request.action_menu_entries.append(ActionMenuEntry(title, url))

config.add_request_method(
lambda request: partial(add_action_menu_entries, request),
'add_action_menu_entries', reify=True)


def main(
global_config: Any, **settings: Any
Expand Down
45 changes: 45 additions & 0 deletions src/privatim/forms/agenda_item_form.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from sqlalchemy import select
from wtforms import StringField
from wtforms import validators
from wtforms.fields.choices import RadioField
from wtforms.fields.simple import TextAreaField
from wtforms.validators import ValidationError

from privatim.forms.core import Form
from privatim.i18n import _
Expand Down Expand Up @@ -41,3 +44,45 @@ def populate_obj(self, obj: 'AgendaItem') -> None: # type:ignore[override]
super().populate_obj(obj)
for name, field in self._fields.items():
field.populate_obj(obj, name)


class AgendaItemCopyForm(Form):

def __init__(
self,
context: Meeting,
request: 'IRequest',
) -> None:

self._title = _('Select Destionation for Agenda Item')

super().__init__(
request.POST,
obj=context,
meta={'context': context, 'request': request},
)

all_meetings_for_choices = [
(str(meeting.id), meeting.name)
# valid destination are all meetings except the one from which
# we are copying from
for meeting in request.dbsession.execute(
select(Meeting).where(Meeting.id != context.id)
).scalars().all()
]
if not all_meetings_for_choices:
self.copy_to.validators.append(
lambda form, field: ValidationError(
_('No valid destination meetings available.')
)
)
self.copy_to.choices = all_meetings_for_choices

copy_to = RadioField(
label=_('Copy to'), validators=[validators.DataRequired()]
)

def populate_obj(self, obj: 'AgendaItem') -> None: # type:ignore[override]
super().populate_obj(obj)
for name, field in self._fields.items():
field.populate_obj(obj, name)
35 changes: 30 additions & 5 deletions src/privatim/forms/consultation_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from privatim.forms.constants import CANTONS_SHORT
from privatim.forms.core import Form
from wtforms import StringField
from wtforms.fields.choices import SelectField
from wtforms.fields.simple import TextAreaField
from wtforms.validators import DataRequired
Expand Down Expand Up @@ -52,17 +51,43 @@ def __init__(
]
self.status.choices = translated_choices

title = StringField(
# If editing, populate the secondary_tags field
# if context and context.secondary_tags:
# self.secondary_tags.process_data(context.secondary_tags)

title = TextAreaField(
_('Title'),
validators=[DataRequired()],
)
description = TextAreaField(_('Description'))
recommendation = StringField(_('Recommendation'))

# Beschreibung
description = TextAreaField(
_('Description'),
render_kw={'rows': 6},
)
# Empfehlung
recommendation = TextAreaField(
_('Recommendation'),
render_kw={'rows': 6},
)

# new Prüfergebnis
evaluation_result = TextAreaField(
_('Evaluation Result'),
render_kw={'rows': 6},
)

# new: Beschluss
decision = TextAreaField(
_('Decision'),
render_kw={'rows': 6},
)

status = SelectField(
_('Status'),
choices=[]
)
cantons = SearchableSelectField(
secondary_tags = SearchableSelectField(
_('Cantons'),
choices=[('', '')] + CANTONS_SHORT,
validators=[
Expand Down
14 changes: 10 additions & 4 deletions src/privatim/forms/fields/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from wtforms.validators import DataRequired
from wtforms.validators import InputRequired
from wtforms.fields import FieldList
from wtforms.fields.choices import SelectField
from wtforms.fields.choices import SelectMultipleField
from wtforms.fields.simple import FileField
from wtforms.widgets.core import Select
from werkzeug.datastructures import MultiDict
Expand Down Expand Up @@ -159,8 +159,10 @@ def process_formdata(self, valuelist: list['RawFormValue']) -> None:
self.data = sedate.replace_timezone(self.data, self.timezone)


class SearchableSelectField(SelectField):
"""A multiple select field with tom-select.js support.
class SearchableSelectField(SelectMultipleField):

"""
A multiple select field with tom-select.js support.
Note: This is unrelated to PostgreSQL full-text search, which also uses
the term 'searchable'.
Expand All @@ -171,7 +173,11 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any:
init_tom_select.need()
return super().__call__(*args, **kwargs)

widget = ChosenSelectWidget(multiple=True)
def process_data(self, value: list[object]) -> None:
if value:
self.data = [
str(v.id) if hasattr(v, 'id') else str(v) for v in value
]


class UploadField(FileField):
Expand Down
67 changes: 67 additions & 0 deletions src/privatim/forms/filter_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from wtforms import SelectField, BooleanField, DateField

from privatim.forms.constants import cantons_named
from privatim.forms.core import Form
from wtforms.validators import Optional
from privatim.i18n import _


from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pyramid.interfaces import IRequest
from wtforms import Field


def render_filter_field(field: 'Field') -> str:
if isinstance(field, BooleanField):
return field(class_="form-check-input")
else:
return field(class_="form-control")


class FilterForm(Form):
def __init__(
self,
request: 'IRequest',
) -> None:

self._title = _('Filter')
session = request.dbsession
super().__init__(request.POST, meta={'dbsession': session})

def get_type_fields(self) -> list[BooleanField]:
return [self.consultation, self.meeting, self.comment]

def get_date_fields(self) -> list[tuple[str, DateField]]:
return [('datumVon', self.start_date), ('datumBis', self.end_date)]

canton: SelectField = SelectField(
_('Kanton'),
choices=[('all', _('all'))] + cantons_named,
validators=[Optional()],
render_kw={'class': 'form-select', 'id': 'kanton'},
)

consultation: BooleanField = BooleanField(
_('Consultation'),
render_kw={'class': 'form-check-input', 'id': 'vernehmlassung'},
)
meeting: BooleanField = BooleanField(
_('Meeting'), render_kw={'class': 'form-check-input', 'id': 'sitzung'}
)
comment: BooleanField = BooleanField(
_('Comment'),
render_kw={'class': 'form-check-input', 'id': 'kommentar'},
)

start_date: DateField = DateField(
_('Date from'),
validators=[Optional()],
render_kw={'class': 'form-control', 'id': 'datumVon'},
)
end_date: DateField = DateField(
_('Date to'),
validators=[Optional()],
render_kw={'class': 'form-control', 'id': 'datumBis'},
)
17 changes: 9 additions & 8 deletions src/privatim/forms/meeting_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ def __init__(
)

attendees = SearchableSelectField(
_('Attendees'),
_('Members'),
validators=[InputRequired()],
)

def validate_name(self, field: 'Field') -> None:
session = self.meta.dbsession
stmt = select(Meeting).where(Meeting.name == field.data)
meeting = session.execute(stmt).scalar()
if meeting:
raise validators.ValidationError(_(
'A meeting with this name already exists.'
))
if self._title == _('Add Meeting'):
session = self.meta.dbsession
stmt = select(Meeting).where(Meeting.name == field.data)
meeting = session.execute(stmt).scalar()
if meeting:
raise validators.ValidationError(_(
'A meeting with this name already exists.'
))

def populate_obj(self, obj: Meeting) -> None: # type:ignore[override]
for name, field in self._fields.items():
Expand Down
2 changes: 2 additions & 0 deletions src/privatim/forms/working_group_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ def __init__(
leader: SelectField = SelectField(_('Leader'))

members: SearchableSelectField = SearchableSelectField(_('Members'))

chairman_contact: StringField = StringField(_('Contact Chairman'))
7 changes: 3 additions & 4 deletions src/privatim/layouts/layout.pt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
<body class="d-flex flex-column h-100">

<!-- Header -->
${panel('navbar')}

${panel('navbar')}
<div class="main-content flex-grow-1">
<!-- Begin page content -->
<main class="flex-shrink-0">
<main class="flex-shrink-0 main-content">

${panel('flash')}

Expand All @@ -31,7 +30,7 @@ ${panel('navbar')}
</div>

<!-- footer -->
${panel('footer')}
${panel('footer')}

</body>
</html>
Loading

0 comments on commit 57633f4

Please sign in to comment.