Compare commits

..

4 commits

Author SHA1 Message Date
Víðir Valberg Guðmundsson 69cfc38771 WIP.
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2024-01-14 12:51:17 +01:00
Víðir Valberg Guðmundsson 922fd0254f Implement django-view-decorator 2024-01-14 12:50:31 +01:00
Víðir Valberg Guðmundsson b756e00a98 Add missing migration 2024-01-14 12:49:53 +01:00
Halfdan Mouritzen 6e9c5204cf Minimal CSS for tables (#26)
Reviewed-on: #26
Co-authored-by: Halfdan Mouritzen <halfdan@robothangarskib.dk>
Co-committed-by: Halfdan Mouritzen <halfdan@robothangarskib.dk>
2024-01-14 12:49:53 +01:00
15 changed files with 247 additions and 58 deletions

View file

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

View file

@ -30,6 +30,7 @@ django==5.0.1
# django-allauth # django-allauth
# django-money # django-money
# django-registries # django-registries
# django-view-decorator
# django-zen-queries # django-zen-queries
# membersystem (pyproject.toml) # membersystem (pyproject.toml)
django-allauth==0.60.0 django-allauth==0.60.0
@ -40,6 +41,8 @@ django-money==3.4.1
# via membersystem (pyproject.toml) # via membersystem (pyproject.toml)
django-registries==0.0.3 django-registries==0.0.3
# via membersystem (pyproject.toml) # via membersystem (pyproject.toml)
django-view-decorator==0.0.4
# via membersystem (pyproject.toml)
django-zen-queries==2.1.0 django-zen-queries==2.1.0
# via membersystem (pyproject.toml) # via membersystem (pyproject.toml)
environs[django]==10.0.0 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.utils.translation import gettext_lazy as _
from django_view_decorator import namespaced_decorator_factory
from .permissions import ADMINISTRATE_MEMBERS from .permissions import ADMINISTRATE_MEMBERS
from .selectors import get_member from .selectors import get_member
@ -12,7 +11,14 @@ from utils.view_utils import render_list
from utils.view_utils import RowAction 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): def membership_overview(request):
memberships = get_memberships(member=request.user) memberships = get_memberships(member=request.user)
current_membership = memberships.current() current_membership = memberships.current()
@ -33,8 +39,18 @@ def membership_overview(request):
) )
@login_required admin_members_view = namespaced_decorator_factory(
@permission_required(ADMINISTRATE_MEMBERS.path) namespace="admin-members",
base_path="admin",
)
@admin_members_view(
paths="members/",
name="list",
login_required=True,
permissions=[ADMINISTRATE_MEMBERS.path],
)
def members_admin(request): def members_admin(request):
users = get_members() users = get_members()
@ -54,15 +70,19 @@ def members_admin(request):
row_actions=[ row_actions=[
RowAction( RowAction(
label=_("View"), label=_("View"),
url_name="admin-members-detail", url_name="admin-members:detail",
url_kwargs={"member_id": "id"}, url_kwargs={"member_id": "id"},
), ),
], ],
) )
@login_required @admin_members_view(
@permission_required(ADMINISTRATE_MEMBERS.path) paths="<int:member_id>/",
name="detail",
login_required=True,
permissions=[ADMINISTRATE_MEMBERS.path],
)
def members_admin_detail(request, member_id): def members_admin_detail(request, member_id):
member = get_member(member_id=member_id) member = get_member(member_id=member_id)
subscription_periods = get_subscription_periods(member=member) subscription_periods = get_subscription_periods(member=member)
@ -70,7 +90,7 @@ def members_admin_detail(request, member_id):
context = { context = {
"member": member, "member": member,
"subscription_periods": subscription_periods, "subscription_periods": subscription_periods,
"base_path": "admin-members", "base_path": "admin-members:list",
} }
return render( return render(

View file

@ -40,6 +40,7 @@ DJANGO_APPS = [
THIRD_PARTY_APPS = [ THIRD_PARTY_APPS = [
"allauth", "allauth",
"allauth.account", "allauth.account",
"django_view_decorator",
"django_registries", "django_registries",
] ]

View file

@ -28,6 +28,7 @@ p, h1, h2, h3, h4, h5, h6 {
--light : #fff; --light : #fff;
--light-dust : #f6f6f6; --light-dust : #f6f6f6;
--dust : #f1f1f1; --dust : #f1f1f1;
--medium-dust : #dadada;
--dark-dust : #bfbfbf; --dark-dust : #bfbfbf;
--fade : #878787; --fade : #878787;
--twilight : #4a4a4a; --twilight : #4a4a4a;
@ -61,6 +62,8 @@ h1, h2, h3, h4, h5, h6 {
a { a {
font-weight : 500; font-weight : 500;
color : var(--splash);
text-decoration : none;
} }
body { body {
@ -249,6 +252,25 @@ button:hover {
opacity : 1.0; 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 { form > div {
margin : 0 0 var(--double-space); margin : 0 0 var(--double-space);
} }

View file

@ -51,7 +51,7 @@
</li> </li>
<li> <li>
<a href="/services" class="{% active_path "services" "current" %}"> <a href="{% url "services:list" %}" class="{% active_path "services:list" "current" %}">
Services Services
</a> </a>
</li> </li>
@ -64,7 +64,7 @@
{% if perms.membership.administrate_memberships %} {% if perms.membership.administrate_memberships %}
<li> <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 Admin
</a> </a>
</li> </li>

View file

@ -1,26 +1,12 @@
"""URLs for the membersystem""" """URLs for the membersystem"""
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.decorators import login_required
from django.urls import include from django.urls import include
from django.urls import path from django.urls import path
from django_view_decorator import include_view_urls
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
urlpatterns = [ urlpatterns = [
path("", login_required(index), name="index"), path("", include_view_urls(extra_modules=["project.views"])),
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("accounts/", include("allauth.urls")), path("accounts/", include("allauth.urls")),
path("_admin/", admin.site.urls), path("_admin/", admin.site.urls),
] ]

View file

@ -1,35 +1,12 @@
from membership.models import ServiceAccess from django_view_decorator import view
from services.registry import ServiceRegistry
from utils.view_utils import render from utils.view_utils import render
@view(
paths="",
name="index",
login_required=True,
)
def index(request): def index(request):
return render(request, "index.html") return render(request, "index.html")
def services_overview(request):
active_services = [
access.service_implementation
for access in ServiceAccess.objects.filter(
user=request.user,
)
]
active_service_classes = [service.__class__ for service in active_services]
services = [
service
for _, service in ServiceRegistry.get_items()
if service not in active_service_classes
]
context = {
"non_active_services": services,
"active_services": active_services,
}
return render(
request=request,
template_name="services_overview.html",
context=context,
)

View file

@ -1,3 +1,4 @@
from django import forms
from django_registries.registry import Interface from django_registries.registry import Interface
from django_registries.registry import Registry from django_registries.registry import Registry
@ -23,3 +24,17 @@ class ServiceInterface(Interface):
# - maybe a list of tuples with the field name and the type of the field # - maybe a list of tuples with the field name and the type of the field
# this could be used to generate a form for the service, and also to validate # this could be used to generate a form for the service, and also to validate
# the data saved in a JSONField on the ServiceAccess model # the data saved in a JSONField on the ServiceAccess model
subscribe_fields: list[tuple[str, forms.Field]] = []
def get_form(self) -> type:
"""Get the form for the service"""
print(self.subscribe_fields)
return type(
"ServiceForm",
(forms.Form,),
{
field_name: field_type
for field_name, field_type in self.subscribe_fields
},
)()

View file

@ -1,3 +1,5 @@
from django import forms
from .registry import ServiceInterface from .registry import ServiceInterface
@ -14,6 +16,10 @@ class MatrixService(ServiceInterface):
url = "https://matrix.data.coop" url = "https://matrix.data.coop"
description = "Matrix service for data.coop" description = "Matrix service for data.coop"
subscribe_fields = [
("username", forms.CharField()),
]
class MastodonService(ServiceInterface): class MastodonService(ServiceInterface):
slug = "mastodon" slug = "mastodon"

View file

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="content-view">
<h2>{{ service.name }}</h2>
</div>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block content %}
<div class="content-view">
<h2>Subscribe to {{ service.name }}</h2>
<form>
{{ form }}
<button type="submit">
Subscribe
</button>
</form>
</div>
{% endblock %}

View file

@ -28,11 +28,16 @@
<div class="description"> <div class="description">
<h3>{{ service.name }}</h3> <h3>{{ service.name }}</h3>
<p>{{ service.description }}</p> <p>{{ service.description }}</p>
<a href="{% url "services:detail" service_slug=service.slug %}">
Read more
</a>
|
<a href="{{ service.url }}" target="_blank"> <a href="{{ service.url }}" target="_blank">
Visit Visit
</a> </a>
</div> </div>
<a>Subscribe</a> <a href="{% url "services:subscribe" service_slug=service.slug %}">Subscribe</a>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -1 +1,86 @@
# Create your views here. # Create your views here.
from django_view_decorator import namespaced_decorator_factory
from membership.models import ServiceAccess
from services.registry import ServiceRegistry
from utils.view_utils import render
services_view = namespaced_decorator_factory(
namespace="services",
base_path="services",
)
@services_view(
paths="",
name="list",
login_required=True,
)
def services_overview(request):
active_services = [
access.service_implementation
for access in ServiceAccess.objects.filter(
user=request.user,
)
]
active_service_classes = [service.__class__ for service in active_services]
services = [
service
for _, service in ServiceRegistry.get_items()
if service not in active_service_classes
]
context = {
"non_active_services": services,
"active_services": active_services,
}
return render(
request=request,
template_name="services/services_overview.html",
context=context,
)
@services_view(
paths="<str:service_slug>/",
name="detail",
login_required=True,
)
def service_detail(request, service_slug):
service = ServiceRegistry.get(slug=service_slug)
context = {
"service": service,
}
return render(
request=request,
template_name="services/service_detail.html",
context=context,
)
@services_view(
paths="<str:service_slug>/subscribe/",
name="subscribe",
login_required=True,
)
def service_subscribe(request, service_slug):
service = ServiceRegistry.get(slug=service_slug)
# TODO: add a form to subscribe to the service
context = {
"service": service,
"base_path": "services:list",
"form": service.get_form(),
}
return render(
request=request,
template_name="services/service_subscribe.html",
context=context,
)