Extending¶
Every policy is a dotted path. Common extension points:
Attendance processor¶
from fingerprint_attendance.processing.default import FirstInLastOutProcessor
class PairedHoursProcessor(FirstInLastOutProcessor):
version = "paired-1"
def build_context(self, enrollee, work_date, punches):
ctx = super().build_context(enrollee, work_date, punches)
pairs = zip(punches[::2], punches[1::2])
ctx.worked = sum((o.punched_at - i.punched_at for i, o in pairs), timedelta())
return ctx
FINGERPRINT_ATTENDANCE["ATTENDANCE_PROCESSOR"] = "myapp.processing.PairedHoursProcessor"
Implement BaseAttendanceProcessor.process_day(enrollee, work_date, create_empty=False) from
scratch if you want full control. process_range, recompute and process_pairs are
provided. Set ATTENDANCE_PROCESSOR = None to disable processing and compute attendance
yourself from Punch rows (Punch.objects.effective() excludes soft duplicates).
Sync strategy¶
from fingerprint_attendance.sync.strategies import BaseSyncStrategy
class SiteStrategy(BaseSyncStrategy):
def devices_for(self, enrollee):
return Device.objects.active().filter(location=enrollee.employee.site)
def enrollees_for(self, device): # optional, for fast backfills
return Enrollee.objects.active().filter(employee__site=device.location)
A plain function (enrollee) -> QuerySet[Device] also works.
Punch state resolver¶
from fingerprint_attendance.ingestion.state import BasePunchStateResolver
class WorkCodeResolver(BasePunchStateResolver):
def resolve(self, candidates, context):
for c in candidates:
c.state = "overtime_in" if c.work_code == "9" else "check_in"
Set needs_history = True to use context.previous_punch_count(candidate).
Dedupe key¶
DEDUPE_KEY_BUILDER = "myapp.dedupe.key" with key(candidate) -> str. The default is
serial|pin|UTC second.
Protocol adapter¶
from fingerprint_attendance.adms.adapter import ADMSProtocolAdapter
class MyFirmwareAdapter(ADMSProtocolAdapter):
def render_set_time(self, device, local):
return f"SET OPTION DateTime={local:%Y%m%d%H%M%S}"
def parse_attlog_line(self, line):
return super().parse_attlog_line(line.replace(";", "\t"))
Set it globally with ADMS_PROTOCOL_ADAPTER, or per device with
device.options["protocol_adapter"]. Add commands with
adms.commands.command_registry.register(CommandSpec(...)).
API¶
Use SERIALIZER_OVERRIDES, VIEWSET_OVERRIDES, FILTERSET_OVERRIDES, per-action
permissions, and the authentication, pagination and throttle settings. See REST API.
Subclass the shipped classes in fingerprint_attendance.api to keep their behaviour.
Models¶
Concrete models are thin subclasses of the abstract bases in
fingerprint_attendance.models.abstract. Reuse them for your own tables, and override the
relation fields, whose reverse names must be unique. Swapping the package's concrete models
is not supported; extend with one-to-one profile models instead.
Everything else¶
Template storage (TEMPLATE_STORAGE_BACKEND, e.g. a vault), PIN generation, day boundaries,
calendar, holiday, leave and schedule providers, the realtime backend and group names, the
webhook sender, and the task backend. See the configuration reference.
Service layer¶
Everything the API does is available as a function in fingerprint_attendance.services, so
you can call it from your own views, admin actions or scripts.