Attendance Admin Guide
What this is for
The attendance list is the digital replacement for the paper list at a meeting. You create an event, the site gives it a public page, and the participants check themselves in and out with a short code that the site shows on a page only the attendance readers can open, and rotates by itself. Nobody has to be checked in by an editor, and no paper has to be passed around, so a room with a screen or a projector can run the whole thing from the site.
The list is a record of who was in the room at a meeting. It is not the association’s minutes, and it does not replace them.
Creating an event
- Open the attendance events list (Närvaroevenemang, under Närvaro in the sidebar) in
/admin and use the add button above the list. It shows each event’s title, whether it is Inte startat, Pågår or Avslutat, its start and end time and whether guests may check in, and each row carries Visa möteskoden for the overview page and Rapport for the record. The status is coloured: green while the meeting is running, amber before it starts and grey once it has ended, which is how the meeting that is running now is found in a list ordered by start time. It opens on the newest event, and can be searched by title or narrowed by date.
- Fill in:
- Titel: the name the participants see, and the name the admin list shows.
- Beskrivning: one optional line shown under the title.
- Slug: the URL segment. An event with the slug
hostmote lives at /attendance/hostmote/. It has to be unique and can contain only letters, digits, hyphens and underscores, so write hostmote rather than höstmöte. The form says the same under the field.
- Starttid and Sluttid: when the event begins and ends. Sluttid is optional. With it, the event leaves the public list once that moment has passed; without it, the event leaves the list 24 hours after its Starttid. Sluttid has to be later than Starttid, and the form refuses a pair the other way round rather than saving it, because the status, the public list and the report all read the two together. The form explains what an empty Sluttid means. A meeting whose end is not known in advance is the ordinary reason to leave it empty: the event page then prints “Slutar: -“, the meeting stays on the list for 24 hours from its start, and the report labels its headcount differently (see The report after the meeting, and
docs/dev/attendance.md for what the number is computed from).
- Tillåt icke-medlemmar att delta: unticked by default, which means the event page asks a visitor without an account to log in. Tick it and save to open the meeting to guests, and a guest can then see the check-in form and write their name. The switch takes effect on the next page load, so tick it before the meeting if guests are expected.
- Kodens giltighetsperiod (sekunder): how long each code is shown before it rotates. 30 is the default, and the field refuses anything below 1 second, because a period of zero breaks the code calculation. A meeting that needs longer to read or scan the code can be given more; a shorter period is harder to pass on to somebody outside the room.
- Kodens genereringsnyckel: the value the codes are computed from. The site fills in a random one, and you can leave it as it is.
- The form is grouped so the meeting comes first and the code machinery second: Mötet holds the fields above, a small section under it holds the three links, and Koden holds the two code fields folded away, which is where to look when the code has to change. Under the links, Öppna incheckningen opens the page the participants use, Visa möteskoden opens the overview page with the current code, and Visa ändringarna opens this meeting’s own change list; all three open in a new tab, so a half-filled form is not left behind. The two times are one field each with a calendar picker, the same widget the events and polls forms use, and a change row’s time is edited with it too. The admin’s own View on site button in the top right opens the same participant page.
- Save. The event appears on
/attendance/ immediately unless its Sluttid has already passed, or it has none and started more than 24 hours ago. Its page is at /attendance/<slug>/ either way, and so is its code.
Running the check-in at the meeting
- Open the event’s page at
/attendance/<slug>/, and follow Till översiktsvyn near the top. Only a reader who holds the attendance permission sees that link.
- The overview page at
/attendance/<slug>/overview shows the current code in large digits with a QR code below it, the Närvarande list under that, and the Närvaroflöde feed last. The code and the QR code change every Kodens giltighetsperiod (sekunder) seconds, and the page updates itself, list, feed and headcount included, so leave it on a screen where the participants can see or scan it. Under the attendee list, the feed is a table with a line per check-in and check-out as it happens, newest first: the time, the name, and whether the person arrived or left. An arrival is written in green and a departure in amber, and the word in the last column says the same thing, so the colours are a shortcut rather than the only signal. A departure followed by a fresh arrival is the same person stepping out and coming back, which is what to point at when somebody asks whether a member really left the room and returned. The feed opens on the ten most recent changes and a Visa fler button reveals the next twenty each time, so the page does not scroll on for ever; when the changes the page brought with it run out, the button gives way to a line linking to the meeting page, whose Närvaroändringar list shows the same changes as a snapshot, and the report holds the whole record and downloads it as a timeline CSV.
-
| The overview page is for readers who hold **Närvaroevenemang |
Can view närvaroevenemang**, and staff status on its own is not enough. An anonymous visitor who opens the address is asked to log in, and a signed-in member without the permission gets an error page whether they are staff or not. That is deliberate: the code is the whole check-in control, so the page that shows it stays with the people running the meeting. See Permissions for how to grant it. |
- Read the code from the screen rather than copying it onto a whiteboard or a printout. It is only valid for its window, so anything written down is stale within seconds.
The headcount in the room
Both the event page and the overview page print how many people are present in the Närvarande heading itself, as in “Närvarande: 7”. On the event page the number is a snapshot from the moment the page was opened, so reload to refresh it, and the names under it are drawn only for a reader who holds the attendance permission. On the overview page the number and the names are live: the number moves as people check in and check out, and it is always the length of the list under it, so the heading and the rows cannot disagree.
The live headcount describes who the attendance log currently counts in the room. It is useful context while a poll is running, but it is not the poll’s exact turnout denominator: guests and members who are not eligible to vote can be present, and attendees may enter or leave between votes. A higher headcount than voter count does not by itself mean that someone eligible has not voted. See docs/admin/polls.md for the poll rules.
Reaching the event page
The site’s menus are editor content, not code, so the attendance page reaches them the way any other link does: add a Static URL entry (under a Static Page Nav category) pointing at /attendance/, and it appears in that dropdown. Until such an entry exists, the address is something you give to the participants.
The event list at /attendance/ is public, and each event has its own address at /attendance/<slug>/. The QR code on the overview page is the shortest route to one event: it encodes the event’s address as a full link, the kind any phone camera app offers to open, with the current code in it, so a participant who scans it lands on the event page with the code already filled in. Nothing needs to be installed for that, and the page’s own Skanna QR-kod button reads the same code. The address it carries is the one the overview page is being served from, so open that page at the site’s own address, which is where the admin is normally reached anyway: a page opened through an internal server name and not through the site would put that name in the code, and a phone in the room could not follow it. Put the event’s address in the meeting invitation if people should be able to find it without the QR code, and remember that a guest still needs Tillåt icke-medlemmar att delta ticked to use it; the switch is off on a new meeting.
Checking people in and out
- A signed-in member opens the event page, types the code (or scans the QR code with a phone camera, which opens the same page with the code prefilled) and presses Gå in. The member is recorded as themselves, and the page then shows them as present, with Gå in disabled and Gå ut ready.
- A visitor without an account types their name in the name box, then the code, and presses Gå in. The name is what the list keeps. If Tillåt icke-medlemmar att delta is unticked, which is how a new meeting starts, there is no name box: the page sends the visitor to the login page instead, and it keeps the address they arrived at, so a visitor who followed a QR code lands back on the check-in page with the room’s code already filled in once they have logged in.
- A guest’s name is remembered in that browser, so the next visit arrives with the name box already filled in and with the same “Du är närvarande” line and disabled button a member gets. Nothing about that is an account: it is the name that was typed, kept in a cookie. Clearing the browser’s cookies costs a guest nothing but typing the name again. Capitalisation and stray spaces do not make a different attendee:
david dahl and ` David Dahl ` are the guest David Dahl already is, and the entry keeps the spelling it was created with.
- Nobody can go in twice or go out when not present. For a signed-in member and for a remembered guest the button that makes no sense is disabled. For everyone, the server refuses the action with a message on the page instead of writing a row: “Du kan inte gå in i ett evenemang var du redan är närvarande” or “Du kan inte gå ut ur ett evenemang var du inte är närvarande”.
- When an anonymous attempt is refused, the form keeps the guest name typed for that attempt. If the field is empty, the remembered name is used instead, including its presence state. A failed attempt never sets or refreshes the cookie.
- The Närvarande list at the bottom of the event page is a snapshot from the moment the page was opened, so reload to see who has arrived since. It is drawn for a reader who holds the attendance permission; a participant sees the number in the heading and no names. A room of three hundred would otherwise push the change log thousands of pixels down, so the list opens on the first ten names and a Visa fler button reveals twenty more each time, the same control the change log below uses. On the overview page the same list is live: it picks up a name as that person checks in and drops it as they check out, next to the code and the QR code, which also update by themselves. The number in the heading is the room’s size on both pages, so it is a snapshot on the event page and live on the overview page. Under the list on the overview page the Närvaroflöde feed follows every change as it happens, which is where to look when the question is whether somebody actually left and came back; the event page’s own Närvaroändringar list is the same data as a snapshot, so reload it there.
- A guest appears on the overview page under the name their row holds, which is the first spelling used, because a later visit that differs only by capitalisation or spacing lands on the same row. The event page’s list adds the marker “(icke-medlem)” after a guest’s name; the overview page leaves it off, because that label is the one the live update carries.
- The number in the Närvarande heading is public on the event page: anyone who can open the page sees how many are in the room. The names in the list under it, guests’ names included, follow the same attendance permission as the change log below, so a participant who is not a reader sees the number and nothing else.
- A guest’s name row is shared from one event to the next, because the name is the whole identity of a guest. Two different people who type the same name share one entry and cannot be told apart, so ask guests for a name that is theirs alone, such as first name and surname. Capitalisation and stray spaces no longer matter in either direction: a guest who types
david dahl or ` David Dahl ` is the entry David Dahl created, which keeps that first spelling on the list, the change log and the report. A different spelling is still a second entry, so Dave Dahl is not David Dahl.
The change log
- The event page shows a Närvaroändringar table to readers who hold the attendance permission, under the attendee list: the time with its date, the person, and the change in Swedish (Anlände or Lämnade), newest first, in the association’s own timezone. An arrival is green and a departure amber, the same colours as the overview page’s feed, so the two read the same way. The table opens on the ten newest changes and a Visa fler button reveals twenty more each time, so a long meeting does not draw every row at once; a reader without JavaScript gets the whole rendered page at once instead, so the button never stands in the way. The page carries at most the newest sixty changes, and when those run out a line links to the report, whose CSV carries the entire meeting and whose page draws the first thousand changes. That line is drawn only for a reader who can open the report, which means staff status as well as the permission. Unlike the feed, this table is a snapshot: reload the page to see changes made since it was opened. It is the same data the attendee list is computed from, so it is what to read when the Närvarande snapshot on the event page looks out of date.
- A meeting’s changes live in a list that belongs to that meeting: Visa ändringarna on the meeting page opens it, and the list is titled with the meeting’s name. There is no all-events list, and opening the list without a meeting sends you back to the meeting list, because a change without its meeting is not something anybody reads. Saving, adding or deleting a row returns to that meeting’s list. One Deltagare column names the person: a member by name and linked to their own page, a guest by the name they typed with the (icke-medlem) marker, the way the site’s own tables show it. The change is green for an arrival and amber for a departure, the same colours as the two tables on the site, so a long list can be read for the departures alone. The event form deliberately carries no inline of the changes, because Django draws one editable form per row and a busy meeting has hundreds, which made the page take minutes to open.
- The change rows can be edited and deleted in the admin like any other row, but deleting one changes who counts as present on the public page, because presence is read from the newest row rather than stored. The correction form picks a member by name rather than by username. Treat a manual edit as a correction of last resort: the report reads the rows as they stand, so it is not an audit trail that keeps what was corrected. An overview page that is open while a row is corrected or deleted reloads itself, so it cannot keep showing the row that was changed.
The report after the meeting
Once the meeting is over, the attendance events list (Närvaroevenemang, under Närvaro) has a Rapport button on every row. It opens a page for that one meeting. Nothing on the page is stored: it is computed from the check-in rows every time it is opened, so there is nothing to maintain and nothing that can go stale.
The page shows the meeting itself (title, description, start and end, slug, whether guests may check in, and its status), then four numbers:
- Närvarande vid slutet or Närvarande vid sista ändringen: how many the site counts as in the room. A meeting with a Sluttid gets the first label, and the number is read at that end, so later check-ins and check-outs are not part of it. A meeting without one gets the second label, and the number is read after the last recorded change. It comes from the log rather than the clock, so it stays the same whenever the report is opened.
- Som mest närvarande: the largest headcount in the room at any one moment. The page replays changes in timeline order to find it. If the meeting has a Sluttid, changes later than that time are excluded from the peak too. With no Sluttid, every recorded change is included.
- Unika deltagare: how many different people ever checked in, so somebody who left and came back counts once.
- Ändringsrader: how many check-ins and check-outs the meeting has in total. The table below and the printout show at most the oldest thousand of them, and the timeline CSV carries every one.
The status names one of three states. Inte startat is a meeting whose Starttid is still ahead, Pågår is one that has started and has no end or has not reached it, and Avslutat is one whose Sluttid has passed. A meeting without a Sluttid is therefore Pågår from its start onwards and never Avslutat: there is no end to compare against, which is the same reason its headcount carries the last-change label below.
The Tidslinje table below is the whole log, oldest first: local time, the name, whether the row is an arrival or a departure, and whether the person is a member or a guest. A meeting with more than a thousand changes draws the first thousand and says so under the heading; both downloads still carry every row. It is the same data as the Närvaroändringar list on the event page, but in the order the meeting happened, which is the order to read it in when writing the minutes. The time column prints a short local date and time on one line, as in 2026-10-16 23:51, so a narrow window does not fold it over several lines.
Under that, every poll attached to the meeting is printed with its question, whether voting has ended, each choice with its votes and share, and three numbers beside each other:
- Röstsedlar is the number of ballots, counted per choice.
- Röstande is the number of signed-in members who voted. A visitor without an account is not counted here.
- Närvarande vid slutet or Närvarande vid sista ändringen is the meeting headcount from above, with the same conditional label as the report and the polls CSV header.
The headcount is contextual, not an exact denominator for a particular poll. It is read at the meeting end or last change, not when each vote was cast, and it can include guests and members who are not eligible to vote. A Flerval poll can also have more ballots than voters, and Vem som helst lets one member vote more than once. Do not read the headcount as the poll’s eligible population. While a vote is running, the poll’s own page shows the current room count and voter count (docs/admin/polls.md), which still are not an exact denominator.
The page is laid out for the screen as well as for paper, in both admin themes. The action links at the top are styled as buttons, and they wrap onto another row on a narrow window instead of being cut off at the edge; Mötet and Visa ändringarna lead back to the meeting and to its change list, so there are five of them, or four when the reader may not see poll questions. Each part of the report sits in its own bordered section under a heading, the number lists run across the line and wrap, and every table fills the width with padded cells and can be scrolled sideways when the window is too narrow for it, so no column is lost and nothing is clipped. On a phone-width window the meeting details stack into one column with a gap between the pairs rather than as a dense block.
Taking the report away
Two buttons at the top of the report download it:
- Ladda ner tidslinjen som CSV is the timeline, one row per check-in or check-out.
- Ladda ner omröstningarna som CSV is one row per poll per choice, carrying the votes, the share, the ballots, the voters and the meeting-end or last-change headcount, with the poll and choice named so that a row still makes sense on its own. The poll-wide numbers repeat on every row of that poll.
Both files are named after the meeting, for example narvaro_hostmote_2024-05-01.csv, and both open directly in Excel. They use a semicolon between columns and start with a byte-order mark, so Swedish Excel reads the UTF-8 names correctly. String data cells beginning with =, +, - or @ get a leading apostrophe so Excel treats them as literal text; that check looks past leading spaces, tabs and line breaks, so a cell cannot slip a formula in behind one. The column headers and numeric values are unchanged.
Printing the report
The report prints from the browser’s own print dialog, with the usual Ctrl+P (Cmd+P on a Mac). The page carries a print stylesheet, so the printed sheet leaves out the admin’s header, the Unfold header, user menu, breadcrumbs, sidebar and the sidebar’s own toggle control, footer and the report’s own download buttons. It prints black text on a white page whatever colours the admin uses on screen, so a dark theme does not carry its backgrounds onto paper. It keeps a heading with the content that follows it, keeps a table row, a list item and a heading from being cut in half by a page break, and repeats a long table’s header row on every page. It does not number the pages, add a header or footer of its own, or make a PDF: that is what a print dialog or a phone’s “save as PDF” is for, and the stylesheet deliberately does no more than what is listed above.
To keep a meeting’s report for the file, print it while the page is open. The address can also be bookmarked and opened later, because the numbers are computed from the stored rows every time the page is loaded.
Non-members in the admin
Guests have a page of their own, Icke-medlemmar under Närvaro: one row per name, with how many change rows carry it, searchable by name. A guest’s row is created the first time their name is used on the public check-in form and is reused for every later event, so an editor adds a guest by having them check in, not by creating anything, and only on a meeting with Tillåt icke-medlemmar att delta ticked. On that page a name can be corrected and nothing else: there is no add, and no delete, because deleting the row would take every change recorded under that name with it, including past meetings and the report. A rename onto a name another guest already has is refused. The row is shared, so a correction appears everywhere the name does.
Deleting events and members
- Deleting an event deletes every attendance change recorded for it, because a change has no meaning outside its event. The guests’ names stay, since they are shared between events. An event with an attached room-only poll cannot be deleted: remove the Närvarokrav link on the poll first. Removing that link deliberately returns the poll to ordinary voting, so do it only when that is intended.
- Deleting a member’s account deletes that member’s own attendance rows with it. The minutes, not this table, are what outlives a member, so a meeting’s record survives even when an account and its attendance history are gone. Export the history first if it has to be kept.
Permissions
-
| Who sees the code, the QR code, the live attendee list, the feed and the change log: whoever holds **Närvaroevenemang |
Can view närvaroevenemang** (attendance.view_attendanceevent). Grant it under Autentisering och auktorisering > Grupper to the groups that should read a meeting’s attendance, and the members of those groups get the overview page, Till översiktsvyn, the names on the event page and the log under them. Take it away and they lose all of it at once. A page that is already open notices at the next message it sends or receives, which is within one code rotation, and then dims the code it last drew and says that the page cannot update itself any more. Superusers always have it. |
- Being staff is group membership, not a checkbox on the member: the groups named in the site’s staff group setting, which for this site are
styrelse, admin, fotograf and rösträknare, plus superusers. That is what decides who may log in to /admin; it does not by itself let anyone read a meeting’s attendance, which is why the permission above exists.
-
| Creating and editing events and change rows in the admin follows the ordinary Django model permissions, so the attendance permissions have to be granted to the group that should create events. Staff status alone lets somebody log in, not edit. Correcting a guest’s name needs the two permissions of the guest model itself, granted under Autentisering och auktorisering > Grupper to the board group: **Deltagare, icke-medlem |
Can view** to show Icke-medlemmar in the sidebar, and **Deltagare, icke-medlem |
Can change** to correct a name. Neither is granted to anyone by installing the app. |
-
| The report is an admin page, so it asks for staff status as well as **Närvaroevenemang |
Can view närvaroevenemang**, and its two downloads ask for the same pair. A member who holds the permission without being staff sees the overview page and the log, but not the report: the event page does not offer them the link, because it could only send them to the admin login. The polls on the report are a second and separate permission: somebody who may not view questions sees a line saying the polls are hidden instead of the results, and cannot download the poll CSV. The timeline download needs the attendance permission only. |
Troubleshooting
“Fel kod” is shown over the code box.
- The code rotated between the participant reading it and pressing the button, and the code they read is now more than one period old. A code keeps working through the period after it is replaced, so somebody who was a few seconds slow is still accepted; anything older than that is not. Read the current code from the overview page and try again.
- Or they are using another event’s code. Every event has its own code, so a code shown for a different event never works here. This is the usual mix-up when several events are open at once.
- If somebody scanned the QR code and still sees this, the code changed while they walked to the door. Point them at the current one, or have them scan again.
The page says “För många felaktiga koder. Försök igen om N sekunder.”
- That browser has entered five wrong codes, so it is locked out for a minute. Nothing was recorded, so ask the participant to wait and try again with the current code. A correct code is refused too while the lockout lasts, which is deliberate.
- The counter lives in that browser’s session, so another browser, a private window or clearing cookies starts fresh. Treat it as a speed bump for a fumbling participant or a careless script, not as a security control: what actually keeps people out is the code rotating on the screen.
- If several participants hit it at once, the code they are reading is probably stale. Reload the overview page and check that the code on it is the one in force.
“Du kan inte gå in i ett evenemang var du redan är närvarande”
- They are already checked in, usually because the button was pressed twice or the page was opened before they went in. Reload the event page: it shows them as present. Nothing else is needed.
- A guest can also hit this by typing a name that is already checked in, since a name is the whole identity of a guest. Capitalisation does not get around it:
david dahl is the David Dahl who is already in. Ask them to use a name that is theirs alone.
“Du kan inte gå ut ur ett evenemang var du inte är närvarande”
- They have not checked in, or they already checked out. Nothing was written, so reload the event page and press Gå in again.
An event is missing from the public list.
- Its Sluttid has passed. The list shows events that have not ended, so clear or change Sluttid if it should be listed again.
- Or it has no Sluttid and its Starttid is more than a day ago. The list forgets a meeting without an end the day after it started, so give it a Sluttid and it reappears while that end is still ahead. An event with no Sluttid is listed for one day from its start and then drops off.
- The event’s own page at
/attendance/<slug>/ still works either way, so a participant who has the link or the QR code can still check in.
- If the page itself is gone, the event was deleted, and its changes went with it.
The code on the overview page is not changing, or the page says “Anslutningen till servern bröts”.
- The overview page updates over a websocket and retries by itself every few seconds, so the message is the page reporting the gap. The code it shows is the one from when the page was loaded and may be stale, which is what produces “Fel kod” for participants. Reload the page and read the current code from the fresh load. If the message stays, tell a developer, and until then reload the page whenever somebody needs a code.
- The same connection carries the live Närvarande list on the overview page, so that list also stands still while the message is shown. A reload brings back both the current code and the current list.
A participant has no account.
- They do not need one, but the meeting has to allow them: Tillåt icke-medlemmar att delta is off on a new meeting, so tick it and save before the meeting. A guest can then type their name on the event page and check in like anyone else.
- While it is unticked the event page asks the visitor to log in and a guest cannot check in at all. The address they arrived at is kept, so a visitor who scanned the QR code comes back to the check-in page with the code already filled in after logging in.
- There is nothing to create for them in the admin, and no guest entry to fill in.
A participant is missing from the Närvarande list even though the change log shows them.
- On the event page the list is a snapshot, so reload the page. The Närvarande list on the overview page is live, so a name should appear there without a reload.
- If their newest change is a departure, they are not present, and the log line says lämnade.