Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aa7115d
Translated using Weblate (Portuguese)
matalari Sep 16, 2025
3728b6f
Translated using Weblate (Portuguese)
matalari Sep 16, 2025
10c3312
Merge branch 'origin/rizoma' into Weblate.
weblate Sep 17, 2025
d2f1e49
Translated using Weblate (Portuguese)
matalari Sep 20, 2025
a2b3f38
Translated using Weblate (Portuguese)
matalari Sep 20, 2025
6b11cad
Translated using Weblate (Portuguese)
matalari Sep 21, 2025
339bac9
Translated using Weblate (Portuguese)
matalari Sep 21, 2025
fe0031b
Translated using Weblate (Portuguese)
matalari Sep 28, 2025
75ace5c
Merge branch 'origin/rizoma' into Weblate.
weblate Sep 29, 2025
b21c086
feat: added views to deleted an ABCD slot template.
Theophile-Madet Sep 29, 2025
5419d0f
feat: added views to deleted an ABCD shift.
Theophile-Madet Sep 29, 2025
f528a1b
feat: added a command to import the shift capabilities for rizoma users.
Theophile-Madet Sep 29, 2025
1fad339
misc: updated translation file.
Theophile-Madet Sep 29, 2025
85faba4
Merge branch 'origin/rizoma' into Weblate.
weblate Sep 29, 2025
ededaba
Translated using Weblate (Portuguese)
matalari Sep 29, 2025
e7c6165
fix: The date is the header for shift detail pages is not translated.
Theophile-Madet Oct 2, 2025
d13b3a0
Update layout for Rizoma version (#663)
emmanuelgratuze Oct 2, 2025
e12fea9
fix: members from coops.pt that are not active (field _isActiveSince)…
Theophile-Madet Oct 2, 2025
3f671e3
feat: added a second shift reminder email 2 days before the shift, fo…
Theophile-Madet Oct 2, 2025
c07900c
misc: removed file that should not be in git
Theophile-Madet Oct 2, 2025
95f7466
fix: calendar loads without too many DB queries again
Theophile-Madet Oct 2, 2025
b2b04c4
misc: small documentation update about syncing user data with coops.pt
Theophile-Madet Oct 6, 2025
2dfcc1e
misc: typo fix in the readme
Theophile-Madet Oct 6, 2025
d84f6bf
Translated using Weblate (Portuguese)
matalari Oct 7, 2025
f596bfa
Merge branch 'origin/rizoma' into Weblate.
weblate Oct 8, 2025
5292dc3
feat: Added a feature flag to block all outgoing emails.
Theophile-Madet Oct 9, 2025
fd4b336
feat: Added a command that creates google calendar events for all fut…
Theophile-Madet Oct 9, 2025
1b127e3
fix: the shift detail pages now checks for capabilities correctly.
Theophile-Madet Oct 11, 2025
39b747f
feat: thresholds for self unregistering and self looking for standins…
Theophile-Madet Oct 11, 2025
b68e5e2
feat: ABCD users can unregister from their not-ABCD shifts but only i…
Theophile-Madet Oct 11, 2025
cb88f23
Translated using Weblate (Portuguese)
Oct 10, 2025
59af0b1
Merge branch 'origin/rizoma' into Weblate.
weblate Oct 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 42 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[![Python code test coverage](https://sonarcloud.io/api/project_badges/measure?project=SuperCoopBerlin_tapir&metric=coverage)](https://sonarcloud.io/summary/new_code?id=SuperCoopBerlin_tapir)
[![Lines of python code](https://sonarcloud.io/api/project_badges/measure?project=SuperCoopBerlin_tapir&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=SuperCoopBerlin_tapir)

Tapir is a member and shift management system for [SuperCoop Berlin](https://supercoop.de) and [Rizoma](https://www.rizomacoop.pt/)
Tapir is a member and shift management system for [SuperCoop Berlin](https://supercoop.de)
and [Rizoma](https://www.rizomacoop.pt/)
Our Vorstand and member office uses Tapir to manage shifts and members, for example their personal data, capabilities,
payments and shift statuses. It is also used for automatic mails and evaluate new applicants.
Members can use Tapir to register or unregister for shifts, search for a stand-in and see their shift status as well as
Expand All @@ -14,13 +15,16 @@ upcoming shifts.
<img src="https://user-images.githubusercontent.com/18083323/179391686-4cfa724f-4847-4859-aba4-f074722d69ca.png" width="68%"/> <img src="https://user-images.githubusercontent.com/18083323/179391799-96f4e204-9bd2-4739-b8f9-3bc25a70f717.JPG" width="22.6%"/>

The Tapir project is developed by SuperCoop members and is licensed under the terms of the [AGPL license](LICENSE.md).
If you're interested in using Tapir for your own coop, please contact [Théo](https://github.com/Theophile-Madet) or [SuperCoop](https://supercoop.de/en/contact/), we'll be very happy to help!
If you're interested in using Tapir for your own coop, please contact [Théo](https://github.com/Theophile-Madet)
or [SuperCoop](https://supercoop.de/en/contact/), we'll be very happy to help!

SuperCoop members can access the system at [https://members.supercoop.de](https://members.supercoop.de).

Tapir (/ˈteɪpər/) was original inspired by the french coop [L'éléfan](https://github.com/elefan-grenoble/gestion-compte), thanks to them for the inspiration.

A fork for community supported agriculture organization exists [here](https://github.com/FoodCoopX/wirgarten-tapir). While they stil both use the name Tapir, they have diverged a lot and don't share any code anymore.
Tapir (/ˈteɪpər/) was original inspired by the french
coop [L'éléfan](https://github.com/elefan-grenoble/gestion-compte), thanks to them for the inspiration.

A fork for community supported agriculture organization exists [here](https://github.com/FoodCoopX/wirgarten-tapir).
While they stil both use the name Tapir, they have diverged a lot and don't share any code anymore.

## Getting started

Expand Down Expand Up @@ -76,13 +80,15 @@ You can do so **without any programming or Python knowledge**! Just choose a tas
## Troubleshooting

* On macOS, in order to set up a local Python `venv`, you might have to install Postgresql to get `psycopg2` working.
Use `brew install postgresql` for that.
Use `brew install postgresql` for that.

## Rizoma version

Currently, two versions of Tapir exist: for SuperCoop on the `master` branch and for Rizoma under the `rizoma` branch.
If you keep the .env file as is, you'll get the SuperCoop version, which has member management and shift management.
By setting the .env file like this, you'll get the rizoma version, which concentrates on shifts and uses an external identity provider.
By setting the .env file like this, you'll get the rizoma version, which concentrates on shifts and uses an external
identity provider.

```dotenv
DEBUG=True
DJANGO_VITE_DEBUG=
Expand All @@ -95,21 +101,38 @@ COOPS_PT_RSA_PUBLIC_KEY_FILE_PATH=invalid_path
TEMPLATE_SUB_FOLDER=rizoma
```

### User synchronization
Every hour, a celery task (`tapir.rizoma.tasks.sync_users_with_coops_pt_backend`) synchronizes the user list and the member list from coops.pt instance (env var `COOPS_PT_API_BASE_URL`) int the Tapir DB.
Changes done in Tapir are not reflected back to coops.pt.
### User synchronization with coops.pt

#### Setup

You need to define 2 environment variables for the user sync to work.

Newly created users can be used to log in right away, there is no need to wait for a sync.
- The API key `COOPS_PT_API_KEY`, which you should get from [...].coops.pt/developers
- The path to the RSA public key file `COOPS_PT_RSA_PUBLIC_KEY_FILE_PATH`.
This key is used to validate the JWT tokens returned by the coops.pt API when logging in.
The key in the file should be in the "PKCS#1 PEM-encoded"-Format.

You can also run `manage.py sync_users_with_coops_pt_backend` to trigger a sync manually.
#### Automated sync

- Every hour, a celery task (`tapir.rizoma.tasks.sync_users_with_coops_pt_backend`) synchronizes the user list and the
member list from coops.pt instance (env var `COOPS_PT_API_BASE_URL`) in the Tapir DB.
- Changes done in Tapir are not reflected back to coops.pt.
- Members deleted from the coops.pt instance will be deleted from the Tapir instance.
- Newly created users can be used to log in right away, there is no need to wait for a sync.
- You can also run `manage.py sync_users_with_coops_pt_backend` to trigger a sync manually.

### Google calendar invites

When members register to a shift, it is possible to send them an invitation to a Google calendar event by mail.
This requires running local instance and the following setup:
- get a google secret json file from your Google app (see [the docs](https://developers.google.com/workspace/calendar/api/quickstart/python))
- on your local machine, set the env var PATH_TO_GOOGLE_CLIENT_SECRET_FILE as path to that file and start your local instance
- run `manage.py get_google_authorized_user_file`, login if necessary and authorize the app
- this will create a `google_user_token.json` file
- copy that file to your server, with the same name and relative path
- on the feature flag page (.../core/featureflag_list), enable the relevant flag: `feature_flags.shifts.google_calendar_events_for_shifts`
- done!

- get a google secret json file from your Google app (
see [the docs](https://developers.google.com/workspace/calendar/api/quickstart/python))
- on your local machine, set the env var PATH_TO_GOOGLE_CLIENT_SECRET_FILE as path to that file and start your local
instance
- run `manage.py get_google_authorized_user_file`, login if necessary and authorize the app
- this will create a `google_user_token.json` file
- copy that file to your server, with the same name and relative path
- on the feature flag page (.../core/featureflag_list), enable the relevant flag:
`feature_flags.shifts.google_calendar_events_for_shifts`
- done!
15 changes: 15 additions & 0 deletions rizoma/static/rizoma-static/css/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import url("./global/theme.css");
@import url("./components/fonts.css");

/* Components */
@import url("./components/icons.css");
@import url("./components/tooltips.css");
@import url("./components/buttons.css");
@import url("./components/card.css");
@import url("./components/nav.css");
@import url("./components/link.css");
@import url("./components/table.css");

/* Pages */
@import url("./pages/about.css");
@import url("./pages/auth.css");
109 changes: 109 additions & 0 deletions rizoma/static/rizoma-static/css/components/buttons.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

/*
--bs-btn-padding-x: 0.75rem;
--bs-btn-padding-y: 0.375rem;
--bs-btn-font-family: ;
--bs-btn-font-size: 1rem;
--bs-btn-font-weight: 400;
--bs-btn-line-height: 1.5;
--bs-btn-color: var(--bs-body-color);
--bs-btn-bg: transparent;
--bs-btn-border-width: var(--bs-border-width);
--bs-btn-border-color: transparent;
--bs-btn-border-radius: var(--bs-border-radius);
--bs-btn-hover-border-color: transparent;
--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
--bs-btn-disabled-opacity: 0.65;
--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);
*/

.btn {
--bs-btn-padding-x: 1.2rem;
--bs-btn-padding-y: 0.4rem;
}

.btn-primary {
--bs-btn-color: #fff;
--bs-btn-bg: var(--bs-primary);
--bs-btn-border-color: var(--bs-primary);

--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: rgb(from var(--bs-primary) r g calc(b * 0.9));
--bs-btn-hover-border-color: rgb(from var(--bs-primary) r g calc(b * 0.9));

--bs-btn-focus-shadow-rgb: 49, 132, 253;

--bs-btn-active-color: #fff;
--bs-btn-active-bg: rgb(from var(--bs-primary) r g calc(b * 0.8));
--bs-btn-active-border-color: rgb(from var(--bs-primary) r g calc(b * 0.8));

--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: var(--riz-color-gray-light);
--bs-btn-disabled-border-color: var(--riz-color-gray-medium);
}

.btn-outline-primary {
--bs-btn-color: var(--bs-primary);
--bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: var(--bs-primary);
--bs-btn-hover-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: 13, 110, 253;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: var(--bs-primary);
--bs-btn-active-border-color: var(--bs-primary);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: var(--bs-primary);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: var(--bs-primary);
--bs-gradient: none;
}

.btn-secondary {
--bs-btn-color: #fff;
--bs-btn-bg: var(--riz-color-gray-medium);
--bs-btn-border-color: var(--riz-color-gray-medium);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: var(--riz-color-gray-dark);
--bs-btn-hover-border-color: var(--riz-color-gray-dark);
--bs-btn-focus-shadow-rgb: 130, 138, 145;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: var(--riz-color-gray-darker);
--bs-btn-active-border-color: var(--riz-color-gray-darker);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: var(--riz-color-gray-light);
--bs-btn-disabled-border-color: var(--riz-color-gray-light);
}


.btn-outline-secondary {
--bs-btn-color: var(--riz-color-gray-medium);
--bs-btn-border-color: var(--riz-color-gray-light);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: var(--riz-color-gray-medium);
--bs-btn-hover-border-color: var(--riz-color-gray-medium);
--bs-btn-focus-shadow-rgb: 108, 117, 125;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: var(--riz-color-gray-medium);
--bs-btn-active-border-color: var(--riz-color-gray-medium);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: var(--riz-color-gray-medium);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: var(--riz-color-gray-medium);
--bs-gradient: none;
}


/** Custom buttons */

.tapir-btn, .tapir-tooltip-item {
display: inline-flex;
gap: 5px;
flex-direction: row;
align-items: center;
}

.tapir-btn > .material-icons {
font-size: 20px;
}
38 changes: 38 additions & 0 deletions rizoma/static/rizoma-static/css/components/card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Default props
--bs-card-spacer-y: 1rem;
--bs-card-spacer-x: 1rem;
--bs-card-title-spacer-y: 0.5rem;
--bs-card-border-width: 1px;
--bs-card-border-color: var(--bs-border-color-translucent);
--bs-card-border-radius: 0.375rem;
--bs-card-box-shadow: ;
--bs-card-inner-border-radius: calc(0.375rem - 1px);
--bs-card-cap-padding-y: 0.5rem;
--bs-card-cap-padding-x: 1rem;
--bs-card-cap-bg: #00000008;
--bs-card-cap-color: ;
--bs-card-height: ;
--bs-card-color: ;
--bs-card-bg: #fff;
--bs-card-img-overlay-padding: 1rem;
--bs-card-group-margin: 0.75rem;
*/

.card {
--bs-card-cap-bg: var(--riz-color-gray-lighter);
--bs-card-cap-padding-y: .85rem;
--bs-card-cap-padding-x: 1rem;
--bs-card-border-color: var(--riz-color-gray-light);
--bs-card-color: var(--riz-color-gray-dark);
}

h5.card-header {
margin-bottom: 0;
font-weight: 700;
font-size: 1rem;
}

.card-body {
font-size: var(--riz-body-font-size);
}
35 changes: 35 additions & 0 deletions rizoma/static/rizoma-static/css/components/icons.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(/static/core/material-icons/MaterialSymbolsRounded.woff2) format('woff2'),
url(/static/core/material-icons/MaterialSymbolsRounded.woff) format('woff');
}


.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;

/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;

/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;

/* Support for IE. */
font-feature-settings: 'liga';
}

3 changes: 3 additions & 0 deletions rizoma/static/rizoma-static/css/components/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.input-group {

}
3 changes: 3 additions & 0 deletions rizoma/static/rizoma-static/css/components/link.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a {
color: var(--bs-primary);
}
80 changes: 80 additions & 0 deletions rizoma/static/rizoma-static/css/components/nav.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* Navbar */
.nav {
/* --bs-nav-link-padding-x: 1rem;
--bs-nav-link-padding-y: 0.5rem;
--bs-nav-link-font-weight: ;
--bs-nav-link-color: var(--bs-link-color);
--bs-nav-link-hover-color: var(--bs-link-hover-color);
--bs-nav-link-disabled-color: var(--bs-secondary-color); */
}

.nav-link {
--bs-nav-link-font-size: 0.95rem;
}

/* Sidebar */
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}

.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding: 2rem 1rem 0;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
display: flex;
flex-direction: column;
gap: 1.5rem;
}

@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}

.sidebar .nav-item {
height: 2.5rem;
}

.nav {
--bs-nav-link-padding-x: .5rem;
--bs-nav-link-padding-y: .5rem;
}

.sidebar .nav-link {
font-weight: 500;
color: var(--bs-black);
height: 2.5rem;
display: flex;
border-radius: var(--bs-border-radius);
line-height: .95em;
/* padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); */
}

.sidebar .nav-link.active {
background-color: var(--riz-color-gray-light);

}

.sidebar .nav-link:hover {
color: #007bff;
}

.sidebar .sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
color: var(--riz-color-gray-medium);
font-weight: 500;
padding-left: 1rem;
}

Loading
Loading