# -*- coding: utf-8 -*-
"""
Schemas for operations on members
"""
import json
from marshmallow import fields, pre_load, post_dump
from .base import BaseSchema, BaseSearchSchema, BaseModel, BaseSearchModel
from ..util import validate_iban
[docs]class NamiKonto(BaseModel):
"""
Holds information about bank account and payment method of the member.
This class is intended to be instantiated by calling the
:meth:`~marshmallow.Schema.load` method on a corresponding data dictionary.
"""
_tabkeys = ['id', 'zahlungsKondition', 'kontoinhaber', 'iban']
def __repr__(self):
return f'<Kontodaten(Id: {self.id})>'
def __str__(self):
return f'{self.zahlungsKondition}'
[docs]class NamiKontoSchema(BaseSchema):
"""
Schema class for bank account and payment method information.
This Schema will be :std:doc:`nested <nesting>` inside a
:class:`MitgliedSchema` and there should be no other use for it.
"""
__model__ = NamiKonto
id = fields.String()
"""int: |NAMI| internal id of this payment details"""
zahlungsKonditionId = fields.Integer(allow_none=True)
"""int: Id corresponding to the kind of payment"""
mitgliedsNummer = fields.Integer()
"""int: |DPSG| member id"""
institut = fields.String()
"""str: Bank"""
kontoinhaber = fields.String()
"""str: Account holder"""
kontonummer = fields.String()
"""str: Account number"""
bankleitzahl = fields.String()
"""str: Bank sort code"""
iban = fields.String(validate=validate_iban)
"""str: |IBAN|"""
bic = fields.String()
"""str: |BIC|"""
zahlungsKondition = fields.String(load_only=True, allow_none=True)
"""str: Kind of payment (e.g. ``'Std Lastschrift'``). This attribute is not
dumped when updating a `Mitglied`."""
[docs] @pre_load
def id_to_str(self, data, **kwargs):
"""
For some reason the |NAMI| gives the id of the payment details as an
:obj:`integer <int>`, but when you want to update a member it has be a
:obj:`string <str>`. Therefore this method converts incoming ids to a
:obj:`str` object before loading them by making use of the
:func:`~marshmallow.decorators.pre_load` decorator.
Args:
data (dict): Data dictionary to be loaded
Returns:
dict: Corrected data dictionary
"""
data['id'] = f'{data["id"]}'
return data
[docs] @post_dump
def double_dump(self, data, **kwargs):
"""
Incoming data sets are nicely formatted :mod:`json` strings which can
be loaded easily into the Schema but when you update a Mitglied all
payment details have to formatted into a :mod:`json` string and all
attributes have to be in a certain order.
To achieve this the :func:`~marshmallow.decorators.post_dump` decorator
is used.
Args:
data (dict): Already dumped data set
Returns:
str: A :mod:`json` formatted string
"""
return json.dumps(data, separators=(',', ':'))
[docs]class SearchMitglied(BaseSearchModel):
"""
Main class for a Mitglied which came up as a search result. Unfortunately
there cannot be just one Mitglied class because the search results lack
crucal imformation (e.g. payment details).
"""
_tabkeys = ['mitgliedsNummer', 'vorname', 'nachname', 'geburtsDatum', 'email']
_field_blacklist = ['representedClass', 'mglType', 'staatsangehoerigkeit',
'status', 'geschlecht', 'eintrittsdatum', 'id',
'wiederverwendenFlag', 'descriptor', 'version',
'lastUpdated', 'id_id']
def __repr__(self):
return f'<SearchMitglied({self.descriptor})>'
def __str__(self):
return f'{self.descriptor}'
[docs] def get_mitglied(self, nami):
"""
Create a real :class:`Mitglied` form the search result by getting the
corresponding data set through the member id.
Args:
nami (:class:`~pynami.nami.NaMi`): Main |NAMI| class
Returns:
Mitglied: The Mitglied object corresponding to this search result.
"""
return nami.mitglied(self.id, 'GET')
[docs]class SearchMitgliedSchema(BaseSearchSchema):
"""
Schema class for :class:`SearchMitglied`.
For some reason attribute naming in the |NAMI| is a bit inconsistent
between a Mitglied which comes up as a search result and one that is
addressed directly by its id.
"""
__model__ = SearchMitglied
entries_austrittsDatum = fields.Date(attribute='austrittsDatum',
allow_none=True)
""":class:`~datetime.date`: Date of the end of membership"""
entries_beitragsarten = fields.String(attribute='beitragsarten')
"""str: Fee type"""
entries_eintrittsdatum = fields.Date(attribute='eintrittsdatum',
allow_none=True)
""":class:`~datetime.date`: Start of membership"""
entries_email = fields.Email(attribute="email", allow_none=True)
"""str: Primary member email"""
entries_emailVertretungsberechtigter = \
fields.Email(attribute="emailVertretungsberechtigter",
allow_none=True)
"""str: Email address of an authorized representative."""
entries_ersteTaetigkeitId = \
fields.Integer(attribute='ersteTaetigkeitId', allow_none=True)
"""int: Id of the first activity. Defaults to ``null``."""
entries_ersteUntergliederungId = \
fields.Integer(attribute='ersteUntergliederungId', allow_none=True)
"""int: Id of the first tier. This may be empty."""
entries_fixBeitrag = fields.String(attribute="fixBeitrag", allow_none=True)
"""str: Defaults to ``null``."""
entries_geburtsDatum = fields.Date(attribute='geburtsDatum')
""":class:`~datetime.date`: Birth date"""
entries_genericField1 = fields.String(attribute="genericField1",
allow_none=True)
"""str: Not sure why these even exist."""
entries_genericField2 = fields.String(attribute="genericField2",
allow_none=True)
"""str: Not sure why these even exist."""
entries_geschlecht = fields.String(attribute='geschlecht')
"""str: Gender"""
entries_id = fields.Integer(attribute='id_')
"""int: |NAMI| internal id (not |DPSG| member nummer)."""
entries_jungpfadfinder = fields.String(attribute='jungpfadfinder')
"""str: Tier field. Not sure what it is for."""
entries_konfession = fields.String(attribute='konfession')
"""str: Confession"""
entries_kontoverbindung = fields.String(attribute='kontoverbindung')
"""str: Account details. For some reason this is not always transmitted and
may therefore be empty."""
entries_lastUpdated = fields.DateTime(attribute='lastUpdated')
""":class:`~datetime.datetime`: Date of the last update"""
entries_mglType = fields.String(attribute='mglType')
"""str: Type of membership (e.g. ``'Mitglied'``)"""
entries_mitgliedsNummer = fields.Integer(attribute='mitgliedsNummer')
"""int: |DPSG| member number"""
entries_nachname = fields.String(attribute='nachname')
"""str: Surname"""
entries_pfadfinder = fields.String(attribute='pfadfinder')
"""str: Tier field. Not sure what it is for."""
entries_rover = fields.String(attribute='rover')
"""str: Tier field. Not sure what it is for."""
entries_rowCssClass = fields.String(attribute='rowCssClass')
"""str: Unused. The purpose could not be dount out."""
entries_spitzname = fields.String(attribute='spitzname')
"""str: Nickname"""
entries_staatangehoerigkeitText = \
fields.String(attribute='staatangehoerigkeitText')
"""str: Extra nationality info. Empty in most cases."""
entries_staatsangehoerigkeit = \
fields.String(attribute='staatsangehoerigkeit')
"""str: Nationality"""
entries_status = fields.String(attribute='status')
"""str: If the member is active or not"""
entries_stufe = fields.String(attribute='stufe')
"""str: Current tier"""
entries_telefax = fields.String(attribute='telefax')
"""str: Fax number. Who uses these anyway today?"""
entries_telefon1 = fields.String(attribute='telefon1')
"""str: First phone number"""
entries_telefon2 = fields.String(attribute='telefon2')
"""str: Second phone number"""
entries_telefon3 = fields.String(attribute='telefon3')
"""str: Third phone number"""
entries_version = fields.Integer(attribute='version')
"""int: History version number"""
entries_vorname = fields.String(attribute='vorname')
"""str: First name"""
entries_wiederverwendenFlag = \
fields.Boolean(attribute='wiederverwendenFlag')
"""bool: If the member data may be used after the membership ends"""
entries_woelfling = fields.String(attribute='woelfling')
"""str: Tier field. Not sure what it is for."""
entries_gruppierung = fields.String(allow_none=True,
attribute='gruppierung')
"""str: Group name including its id"""
entries_gruppierungId = fields.String(allow_none=True,
attribute='gruppierungId')
"""str: Group id as a string"""
[docs]class Mitglied(BaseModel):
"""
Main class representing a |NAMI| Mitglied
This class overwrites the :meth:`~object.__getattr__` and
:meth:`~object.__setattr__` methods so that attributes of this class can be
handled in a convenient way. It is intended to be instantiated by calling
the :meth:`~marshmallow.Schema.load` method on a corresponding data
dictionary.
"""
_tabkeys = ['mitgliedsNummer', 'vorname', 'nachname', 'geschlecht', 'stufe',
'geburtsDatum', 'strasse', 'plz', 'ort']
_field_blacklist = ['genericField1']
def __repr__(self):
return f'<Mitglied({self.nachname}, {self.vorname})>'
def __str__(self):
return f'{self.vorname} {self.nachname}'
[docs] def update(self, nami):
"""
Writes the possibly changed values to the |NAMI|
Args:
nami (:class:`~pynami.nami.NaMi`): Main class for communication
with the |NAMI|
Returns:
Mitglied: The new Mitglied as it is returned by the |NAMI|
"""
userjson = MitgliedSchema().dump(self)
return nami.mitglied(self.id, 'PUT', json=userjson)
[docs]class MitgliedSchema(BaseSchema):
"""
Schema class for a :class:`Mitglied`
"""
__model__ = Mitglied
austrittsDatum = fields.Date(allow_none=True, load_only=True)
""":class:`~datetime.date`: Date of the end of membership"""
beitragsart = fields.String(allow_none=True)
"""str: Fee type"""
beitragsartId = fields.Integer(allow_none=True)
"""int: Id of the fee type"""
eintrittsdatum = fields.Date()
""":class:`~datetime.date`: Start of membership"""
email = fields.Email(allow_none=True)
"""str: Primary member email"""
emailVertretungsberechtigter = fields.Email(allow_none=True)
"""str: Email address of an authorized representative."""
ersteTaetigkeit = fields.String(allow_none=True)
"""str: First activity. May be empty."""
ersteTaetigkeitId = fields.Integer(allow_none=True, load_only=True)
"""int: Id of the first activity. Defaults to ``null``."""
ersteUntergliederung = fields.String(allow_none=True)
"""str: First tier. May be empty"""
ersteUntergliederungId = fields.Integer(allow_none=True, load_only=True)
"""int: Id of the first tier. This may be empty."""
fixBeitrag = fields.String(allow_none=True)
"""str: Defaults to ``null``."""
geburtsDatum = fields.Date()
""":class:`~datetime.date`: Birth date"""
genericField1 = fields.String(allow_none=True)
"""str: Not sure why these even exist."""
genericField2 = fields.String(allow_none=True)
"""str: Not sure why these even exist."""
geschlecht = fields.String()
"""str: Gender"""
geschlechtId = fields.Integer(allow_none=True)
"""int: Gender id"""
gruppierung = fields.String()
"""str: Group name including its id"""
gruppierungId = fields.Integer(allow_none=True)
"""int: Group id
.. note::
In the search result (see :class:`SearchMitgliedSchema`) this comes as
a :obj:`str`.
"""
id = fields.Integer()
"""int: |NAMI| internal id (not |DPSG| member nummer)."""
jungpfadfinder = fields.String(allow_none=True)
"""str: Tier field. Not sure what it is for."""
konfession = fields.String(allow_none=True)
"""str: Confession"""
konfessionId = fields.Integer(allow_none=True)
"""int: Id of the confession"""
kontoverbindung = fields.Nested(NamiKontoSchema)
""":class:`NamiKontoSchema`: Account details. In a search result this comes
as a :obj:`str`."""
land = fields.String()
"""str: Address country"""
landId = fields.Integer()
"""int: Id of the address country"""
lastUpdated = fields.DateTime(load_only=True)
""":class:`~datetime.datetime`: Date of the last update. This value is not
dumped when updating a :class:`Mitglied`."""
mglType = fields.String()
"""str: Type of membership (e.g. ``'Mitglied'``)"""
mglTypeId = fields.String(load_only=True)
"""str: Id of the type of membership (typically just the value in
uppercase)."""
mitgliedsNummer = fields.Integer(load_only=True)
"""int: |DPSG| member number"""
nachname = fields.String()
"""str: Surname"""
nameZusatz = fields.String(allow_none=True)
"""str: Extra name info"""
ort = fields.String(allow_none=True)
"""str: Address city"""
pfadfinder = fields.String(allow_none=True)
"""str: Tier field. Not sure what it is for."""
plz = fields.String(allow_none=True)
"""str: Postal code"""
region = fields.String(allow_none=True)
"""str: A `Bundesland` or foreign country"""
regionId = fields.Integer(allow_none=True)
"""int: Region id"""
rover = fields.String(allow_none=True)
"""str: Tier field. Not sure what it is for."""
sonst01 = fields.Boolean(allow_none=True)
"""bool: Another generic field. Defaults to :data:`False`."""
sonst02 = fields.Boolean(allow_none=True)
"""bool: Another generic field. Defaults to :data:`False`."""
spitzname = fields.String(allow_none=True)
"""str: Nickname"""
staatsangehoerigkeit = fields.String(allow_none=True)
"""str: Nationality"""
staatsangehoerigkeitId = fields.Integer(allow_none=True)
"""int: Id of the nationality"""
staatsangehoerigkeitText = fields.String(allow_none=True)
"""str: Extra nationality info. Empty in most cases."""
status = fields.String(allow_none=True)
"""str: If the member is active or not"""
strasse = fields.String(allow_none=True)
"""str: Address information"""
stufe = fields.String(allow_none=True)
"""str: Current tier"""
telefax = fields.String(allow_none=True)
"""str: Fax number. Who uses these anyway today?"""
telefon1 = fields.String(allow_none=True)
"""str: First phone number"""
telefon2 = fields.String(allow_none=True)
"""str: Second phone number"""
telefon3 = fields.String(allow_none=True)
"""str: Third phone number"""
version = fields.Integer()
"""int: History version number"""
vorname = fields.String(allow_none=True)
"""str: First name"""
wiederverwendenFlag = fields.Boolean()
"""bool: If the member data may be used after the membership ends"""
woelfling = fields.String(allow_none=True)
"""str: Tier field. Not sure what it is for."""
zeitschriftenversand = fields.Boolean(allow_none=True)
"""bool: If the member gets the |DPSG| newspaper."""