Compare commits

...

7 Commits

Author SHA1 Message Date
Halfdan Mouritzen 498f297ce4 Merge of main
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2024-02-29 20:45:03 +01:00
Víðir Valberg Guðmundsson bdc2d8717c Add devenv files.
continuous-integration/drone/push Build is passing Details
2024-02-10 10:55:38 +01:00
Víðir Valberg Guðmundsson f99c7ee698 Fetch permissions before rendering templates.
continuous-integration/drone/push Build is passing Details
2024-02-10 10:50:23 +01:00
Víðir Valberg Guðmundsson a098a0b032 Add logging.
continuous-integration/drone/push Build is passing Details
2024-02-09 22:04:30 +01:00
Víðir Valberg Guðmundsson f31cd62351 Implement django-view-decorator
continuous-integration/drone/push Build is passing Details
2024-01-14 12:27:36 +01:00
Víðir Valberg Guðmundsson b8a970d5fe Add missing migration 2024-01-14 12:14:51 +01:00
Halfdan Mouritzen 9dda7670df Minimal CSS for tables (#26)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #26
Co-authored-by: Halfdan Mouritzen <halfdan@robothangarskib.dk>
Co-committed-by: Halfdan Mouritzen <halfdan@robothangarskib.dk>
2024-01-14 11:10:55 +00:00
13 changed files with 498 additions and 30 deletions

241
devenv.lock Normal file
View File

@ -0,0 +1,241 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1707004164,
"narHash": "sha256-9Hr8onWtvLk5A8vCEkaE9kxA0D7PR62povFokM1oL5Q=",
"owner": "cachix",
"repo": "devenv",
"rev": "0e68853bb27981a4ffd7a7225b59ed84f7180fc7",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1707451808,
"narHash": "sha256-UwDBUNHNRsYKFJzyTMVMTF5qS4xeJlWoeyJf+6vvamU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "442d407992384ed9c0e6d352de75b69079904e4e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-python": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1707114737,
"narHash": "sha256-ZXqv2epXAjDjfWbYn+yy4VOmW+C7SuUBoiZkkDoSqA4=",
"owner": "cachix",
"repo": "nixpkgs-python",
"rev": "f34ed02276bc08fe1c91c1bf0ef3589d68028878",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "nixpkgs-python",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1704874635,
"narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1707347730,
"narHash": "sha256-0etC/exQIaqC9vliKhc3eZE2Mm2wgLa0tj93ZF/egvM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1707297608,
"narHash": "sha256-ADjo/5VySGlvtCW3qR+vdFF4xM9kJFlRDqcC9ZGI8EA=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0db2e67ee49910adfa13010e7f012149660af7f0",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"nixpkgs-python": "nixpkgs-python",
"pre-commit-hooks": "pre-commit-hooks"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

19
devenv.nix Normal file
View File

@ -0,0 +1,19 @@
{ pkgs, ... }:
{
languages.python.enable = true;
languages.python.version = "3.12";
services.postgres = {
enable = true;
package = pkgs.postgresql_15;
initialDatabases = [ {"name" = "postgres";} ];
listen_addresses = "localhost";
initialScript = "create user postgres with password 'postgres' superuser;";
};
processes = {
app.exec = "while ! pg_isready -d postgres -h localhost -U postgres 2>/dev/null; do sleep 1; done; hatch run server";
};
}

5
devenv.yaml Normal file
View File

@ -0,0 +1,5 @@
inputs:
nixpkgs:
url: github:NixOS/nixpkgs/nixpkgs-unstable
nixpkgs-python:
url: github:cachix/nixpkgs-python

View File

@ -21,6 +21,7 @@ dependencies = [
"whitenoise==6.6.0",
"django-zen-queries==2.1.0",
"django-registries==0.0.3",
"django-view-decorator==0.0.4",
]
dynamic = ["version"]

View File

@ -30,6 +30,7 @@ django==5.0.1
# django-allauth
# django-money
# django-registries
# django-view-decorator
# django-zen-queries
# membersystem (pyproject.toml)
django-allauth==0.60.0
@ -40,6 +41,8 @@ django-money==3.4.1
# via membersystem (pyproject.toml)
django-registries==0.0.3
# via membersystem (pyproject.toml)
django-view-decorator==0.0.4
# via membersystem (pyproject.toml)
django-zen-queries==2.1.0
# via membersystem (pyproject.toml)
environs[django]==10.0.0

View File

@ -0,0 +1,41 @@
# Generated by Django 5.0.1 on 2024-01-14 11:14
import djmoney.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounting", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="order",
name="price_currency",
field=djmoney.models.fields.CurrencyField(
choices=[("DKK", "DKK")], default=None, editable=False, max_length=3
),
),
migrations.AlterField(
model_name="order",
name="vat_currency",
field=djmoney.models.fields.CurrencyField(
choices=[("DKK", "DKK")], default=None, editable=False, max_length=3
),
),
migrations.AlterField(
model_name="payment",
name="amount_currency",
field=djmoney.models.fields.CurrencyField(
choices=[("DKK", "DKK")], default=None, editable=False, max_length=3
),
),
migrations.AlterField(
model_name="transaction",
name="amount_currency",
field=djmoney.models.fields.CurrencyField(
choices=[("DKK", "DKK")], default=None, editable=False, max_length=3
),
),
]

View File

@ -1,6 +1,5 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import permission_required
from django.utils.translation import gettext_lazy as _
from django_view_decorator import namespaced_decorator_factory
from .permissions import ADMINISTRATE_MEMBERS
from .selectors import get_member
@ -12,7 +11,14 @@ from utils.view_utils import render_list
from utils.view_utils import RowAction
@login_required
member_view = namespaced_decorator_factory(namespace="member", base_path="membership")
@member_view(
paths="",
name="membership-overview",
login_required=True,
)
def membership_overview(request):
memberships = get_memberships(member=request.user)
current_membership = memberships.current()
@ -33,8 +39,18 @@ def membership_overview(request):
)
@login_required
@permission_required(ADMINISTRATE_MEMBERS.path)
admin_members_view = namespaced_decorator_factory(
namespace="admin-members",
base_path="admin",
)
@admin_members_view(
paths="members/",
name="list",
login_required=True,
permissions=[ADMINISTRATE_MEMBERS.path],
)
def members_admin(request):
users = get_members()
@ -54,15 +70,19 @@ def members_admin(request):
row_actions=[
RowAction(
label=_("View"),
url_name="admin-members-detail",
url_name="admin-members:detail",
url_kwargs={"member_id": "id"},
),
],
)
@login_required
@permission_required(ADMINISTRATE_MEMBERS.path)
@admin_members_view(
paths="<int:member_id>/",
name="detail",
login_required=True,
permissions=[ADMINISTRATE_MEMBERS.path],
)
def members_admin_detail(request, member_id):
member = get_member(member_id=member_id)
subscription_periods = get_subscription_periods(member=member)
@ -70,7 +90,7 @@ def members_admin_detail(request, member_id):
context = {
"member": member,
"subscription_periods": subscription_periods,
"base_path": "admin-members",
"base_path": "admin-members:list",
}
return render(

View File

@ -40,6 +40,7 @@ DJANGO_APPS = [
THIRD_PARTY_APPS = [
"allauth",
"allauth.account",
"django_view_decorator",
]
LOCAL_APPS = [
@ -153,6 +154,26 @@ ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
ACCOUNT_USERNAME_REQUIRED = False
# Logging
# We want to log everything to stdout in docker
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
},
},
"loggers": {
"": {
"handlers": ["console"],
"level": "DEBUG",
},
},
}
if DEBUG:
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
MIDDLEWARE += [

View File

@ -94,7 +94,7 @@ h6 {
}
a {
font-weight: 500;
<<<<<<< HEAD font-weight: 500;
color: var(--splash);
text-decoration: none;
cursor: pointer;
@ -105,6 +105,10 @@ hr {
height: 0;
border: 0;
border-bottom: 1px solid var(--dark-custard);
=======font-weight: 500;
color: var(--splash);
text-decoration: none;
>>>>>>>bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca
}
body {
@ -263,7 +267,12 @@ div.services {
div.services>div,
div.infobox {
background: var(--light);
<<<<<<< HEAD background: var(--light);
padding: var(--double-space);
border-radius: 6px;
flex: 240px;
max-width: 420px;
=======background: var(--light);
padding: var(--double-space);
border-radius: 6px;
flex: 240px;
@ -285,6 +294,111 @@ div.services>div>div.description>p {
margin-top: var(--half-space);
}
div.services>div>a,
a.button,
button {
display: block;
color: var(--light);
background: var(--splash);
padding: var(--space) var(--double-space);
border-radius: 3px;
opacity: 0.9;
cursor: pointer;
text-align: center;
border: 0;
font-weight: 600;
letter-spacing: 0.03em;
text-decoration: none;
}
div.services>div>a:hover,
a.button:hover,
button:hover {
opacity: 1.0;
}
article table {
width: 100%;
border-spacing: 0;
margin: var(--space) 0;
}
article table thead th {
text-align: left;
}
article table tbody tr:nth-child(odd) {
background: var(--medium-dust);
}
article table thead th,
article table tbody td {
padding: var(--half-space);
}
form>div {
margin: 0 0 var(--double-space);
}
form>div>label {
display: block;
margin: 0 0 6px;
}
form>div>input[type="text"],
form>div>input[type="password"] {
border: 2px solid var(--twilight);
border-radius: 6px;
padding: 8px;
background: var(--light-dust);
width: 100%;
}
#login {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#loginbox {
border-radius: var(--space);
border: 6px solid white;
width: 800px;
height: 500px;
display: flex;
flex-flow: row;
}
#loginbox>div {
padding: var(--double-space);
flex: 1;
}
#loginbox label {
color: var(--twilight);
}
#loginbox>div.login {
background: var(--light-dust);
>>>>>>>bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca display: flex;
flex-flow: column;
justify-content: space-between;
}
div.infobox button {
margin-top: var(--double-space);
}
div.services>div>div.description {
margin-bottom: var(--double-space);
}
div.services>div>div.description>p {
margin-top: var(--half-space);
}
div.services>div>a,
a.button,
button {
@ -387,7 +501,7 @@ input[type="email"] {
padding: 8px;
background: var(--light-dust);
width: 100%;
color : var(--dark);
color: var(--dark);
}
form fieldset {
@ -530,4 +644,4 @@ span.time_remaining {
.pagination .page-item.disabled .page-link {
cursor: default;
}
}

View File

@ -94,7 +94,7 @@
{% if perms.membership.administrate_memberships %}
<li>
<a href="{% url "admin-members" %}" class="{% active_path "admin-members" "current" %}">
<a href="{% url "admin-members:list" %}" class="{% active_path "admin-members:list" "current" %}">
Admin
</a>
</li>

View File

@ -1,26 +1,12 @@
"""URLs for the membersystem"""
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.decorators import login_required
from django.urls import include
from django.urls import path
from .views import index
from .views import services_overview
from membership.views import members_admin
from membership.views import members_admin_detail
from membership.views import membership_overview
from django_view_decorator import include_view_urls
urlpatterns = [
path("", login_required(index), name="index"),
path("services/", login_required(services_overview), name="services"),
path("membership/", membership_overview, name="membership-overview"),
path("admin/members/", members_admin, name="admin-members"),
path(
"admin/members/<int:member_id>/",
members_admin_detail,
name="admin-members-detail",
),
path("", include_view_urls(extra_modules=["project.views"])),
path("accounts/", include("allauth.urls")),
path("_admin/", admin.site.urls),
]

View File

@ -1,9 +1,21 @@
from django_view_decorator import view
from utils.view_utils import render
@view(
paths="",
name="index",
login_required=True,
)
def index(request):
return render(request, "index.html")
@view(
paths="services/",
name="services",
login_required=True,
)
def services_overview(request):
return render(request, "services_overview.html")

View File

@ -120,4 +120,9 @@ def render(request, template_name, context=None):
if context is None:
context = {}
context = base_context(request) | context
# Make sure to fetch all permissions before rendering the template
# otherwise django-zen-queries will complain about database queries.
request.user.get_all_permissions()
return zen_queries_render(request, template_name, context)